Compare commits
No commits in common. "f9094f6fca936a5f29bbcbd3a1fcbf64aac446dc" and "27cf89c736432c6fc693a59e2992e8b3ce20839f" have entirely different histories.
f9094f6fca
...
27cf89c736
28 changed files with 101 additions and 1543 deletions
|
|
@ -1,9 +1,9 @@
|
||||||
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
|
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ["NuGet.Config", "."]
|
COPY ["NuGet.Config", "."]
|
||||||
COPY ["SecondBotEvents/SecondBotEvents.csproj", "SecondBotEvents/"]
|
COPY ["SecondBotEvents/SecondBotEvents.csproj", "SecondBotEvents/"]
|
||||||
|
|
@ -27,8 +27,6 @@ ENV basic_Username='' \
|
||||||
basic_LoginURI='secondlife' \
|
basic_LoginURI='secondlife' \
|
||||||
basic_LogCommands='true' \
|
basic_LogCommands='true' \
|
||||||
basic_DefaultHoverHeight=0.1 \
|
basic_DefaultHoverHeight=0.1 \
|
||||||
basic_MAC='' \
|
|
||||||
basic_Id0='' \
|
|
||||||
commands_AllowFundsCommands='false' \
|
commands_AllowFundsCommands='false' \
|
||||||
commands_AllowIMcontrol='false' \
|
commands_AllowIMcontrol='false' \
|
||||||
commands_SharedSecret='examplecode' \
|
commands_SharedSecret='examplecode' \
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>LibreMetaverse.StructuredData</AssemblyName>
|
<AssemblyName>LibreMetaverse.StructuredData</AssemblyName>
|
||||||
<Description>LibreMetaverse structured data library</Description>
|
<Description>LibreMetaverse structured data library</Description>
|
||||||
<PackageId>LibreMetaverse.StructuredData</PackageId>
|
<PackageId>LibreMetaverse.StructuredData</PackageId>
|
||||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RootNamespace>LibreMetaverse.StructuredData</RootNamespace>
|
<RootNamespace>LibreMetaverse.StructuredData</RootNamespace>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>LibreMetaverse.Types</AssemblyName>
|
<AssemblyName>LibreMetaverse.Types</AssemblyName>
|
||||||
<PackageId>LibreMetaverse.Types</PackageId>
|
<PackageId>LibreMetaverse.Types</PackageId>
|
||||||
<Description>LibreMetaverse type library</Description>
|
<Description>LibreMetaverse type library</Description>
|
||||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RootNamespace>LibreMetaverse</RootNamespace>
|
<RootNamespace>LibreMetaverse</RootNamespace>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
|
|
||||||
|
|
@ -375,11 +375,11 @@ namespace OpenMetaverse
|
||||||
public ulong TimeOfDay { get; private set; }
|
public ulong TimeOfDay { get; private set; }
|
||||||
|
|
||||||
/// <summary>A dictionary of all the regions, indexed by region name</summary>
|
/// <summary>A dictionary of all the regions, indexed by region name</summary>
|
||||||
public Dictionary<string, GridRegion> Regions = new Dictionary<string, GridRegion>();
|
internal Dictionary<string, GridRegion> Regions = new Dictionary<string, GridRegion>();
|
||||||
/// <summary>A dictionary of all the regions, indexed by region handle</summary>
|
/// <summary>A dictionary of all the regions, indexed by region handle</summary>
|
||||||
public Dictionary<ulong, GridRegion> RegionsByHandle = new Dictionary<ulong, GridRegion>();
|
internal Dictionary<ulong, GridRegion> RegionsByHandle = new Dictionary<ulong, GridRegion>();
|
||||||
/// <summary>A dictionary of regions by region handle</summary>
|
/// <summary>A dictionary of regions by region handle</summary>
|
||||||
public Dictionary<UUID, ulong> RegionsByUUID = new Dictionary<UUID, ulong>();
|
internal Dictionary<UUID, ulong> RegionsByUUID = new Dictionary<UUID, ulong>();
|
||||||
|
|
||||||
private readonly GridClient Client;
|
private readonly GridClient Client;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>LibreMetaverse</AssemblyName>
|
<AssemblyName>LibreMetaverse</AssemblyName>
|
||||||
<PackageId>LibreMetaverse</PackageId>
|
<PackageId>LibreMetaverse</PackageId>
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<NoWarn>$(NoWarn);CS0419;CS8981;CS1591;CS1574</NoWarn>
|
<NoWarn>$(NoWarn);CS0419;CS8981;CS1591;CS1574</NoWarn>
|
||||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
<OutputPath>..\bin\</OutputPath>
|
<OutputPath>..\bin\</OutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using OpenMetaverse.Assets;
|
|
||||||
using SecondBotEvents.Services;
|
using SecondBotEvents.Services;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -8,90 +7,8 @@ using System.Linq;
|
||||||
namespace SecondBotEvents.Commands
|
namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
[ClassInfo("Animations and Gestures")]
|
[ClassInfo("Animations and Gestures")]
|
||||||
public class AnimationCommands(EventsSecondBot setmaster) : CommandsAPI(setmaster)
|
internal class AnimationCommands(EventsSecondBot setmaster) : CommandsAPI(setmaster)
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<UUID, string> gestureTriggerCache = new Dictionary<UUID, string>();
|
|
||||||
private static readonly Dictionary<UUID, string> gestureReplaceCache = new Dictionary<UUID, string>();
|
|
||||||
private static bool isPreCachingStarted = false;
|
|
||||||
private static bool isPreCachingActive = false;
|
|
||||||
private static readonly string cacheFilePath = "data/gesture_cache.json";
|
|
||||||
private static bool isCacheLoaded = false;
|
|
||||||
private static readonly object cacheLock = new object();
|
|
||||||
|
|
||||||
public class GestureCacheEntry
|
|
||||||
{
|
|
||||||
public string Trigger { get; set; }
|
|
||||||
public string ReplaceWith { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void LoadGestureCache()
|
|
||||||
{
|
|
||||||
lock (cacheLock)
|
|
||||||
{
|
|
||||||
if (isCacheLoaded) return;
|
|
||||||
isCacheLoaded = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
System.IO.Directory.CreateDirectory("data");
|
|
||||||
if (System.IO.File.Exists(cacheFilePath))
|
|
||||||
{
|
|
||||||
string json = System.IO.File.ReadAllText(cacheFilePath);
|
|
||||||
var loaded = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, GestureCacheEntry>>(json);
|
|
||||||
if (loaded != null)
|
|
||||||
{
|
|
||||||
lock (gestureTriggerCache)
|
|
||||||
{
|
|
||||||
foreach (var kvp in loaded)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(kvp.Key, out UUID uuid))
|
|
||||||
{
|
|
||||||
gestureTriggerCache[uuid] = kvp.Value.Trigger;
|
|
||||||
gestureReplaceCache[uuid] = kvp.Value.ReplaceWith;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogFormater.Warn("Error loading gesture cache: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SaveGestureCache()
|
|
||||||
{
|
|
||||||
lock (cacheLock)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
System.IO.Directory.CreateDirectory("data");
|
|
||||||
var dump = new Dictionary<string, GestureCacheEntry>();
|
|
||||||
lock (gestureTriggerCache)
|
|
||||||
{
|
|
||||||
foreach (var kvp in gestureTriggerCache)
|
|
||||||
{
|
|
||||||
string replaceWith = "";
|
|
||||||
gestureReplaceCache.TryGetValue(kvp.Key, out replaceWith);
|
|
||||||
dump[kvp.Key.ToString()] = new GestureCacheEntry
|
|
||||||
{
|
|
||||||
Trigger = kvp.Value,
|
|
||||||
ReplaceWith = replaceWith ?? ""
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string json = System.Text.Json.JsonSerializer.Serialize(dump);
|
|
||||||
System.IO.File.WriteAllText(cacheFilePath, json);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogFormater.Warn("Error saving gesture cache: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Toggles if animation requests from this avatar (used for remote poseballs) are accepted")]
|
[About("Toggles if animation requests from this avatar (used for remote poseballs) are accepted")]
|
||||||
[ReturnHints("Granted perm animation")]
|
[ReturnHints("Granted perm animation")]
|
||||||
[ReturnHints("Removed perm animation")]
|
[ReturnHints("Removed perm animation")]
|
||||||
|
|
@ -124,10 +41,8 @@ namespace SecondBotEvents.Commands
|
||||||
InventoryItem itm = GetClient().Inventory.FetchItem(gestureUUID, GetClient().Self.AgentID, TimeSpan.FromSeconds(15));
|
InventoryItem itm = GetClient().Inventory.FetchItem(gestureUUID, GetClient().Self.AgentID, TimeSpan.FromSeconds(15));
|
||||||
if (itm == null)
|
if (itm == null)
|
||||||
{
|
{
|
||||||
return BasicReply("unable to get gesture from inventory", [gesture]);
|
return BasicReply("unable to get getsture from inventory", [gesture]);
|
||||||
}
|
}
|
||||||
LogFormater.Info($"Playing gesture: {itm.Name} ({itm.AssetUUID})");
|
|
||||||
GetClient().Self.ActivateGesture(itm.UUID, itm.AssetUUID);
|
|
||||||
GetClient().Self.PlayGesture(itm.AssetUUID);
|
GetClient().Self.PlayGesture(itm.AssetUUID);
|
||||||
return BasicReply("Accepted", [gesture]);
|
return BasicReply("Accepted", [gesture]);
|
||||||
}
|
}
|
||||||
|
|
@ -146,339 +61,6 @@ namespace SecondBotEvents.Commands
|
||||||
return BasicReply("Accepted", [gesture]);
|
return BasicReply("Accepted", [gesture]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Plays a built-in animation by UUID or name")]
|
|
||||||
[ArgHints("animation", "UUID or name of the animation", "STRING")]
|
|
||||||
[CmdTypeDo()]
|
|
||||||
public object PlayAnimation(string animation)
|
|
||||||
{
|
|
||||||
UUID animID;
|
|
||||||
if (!UUID.TryParse(animation, out animID))
|
|
||||||
{
|
|
||||||
var field = typeof(Animations).GetField(animation.ToUpper(), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
|
||||||
if (field != null)
|
|
||||||
{
|
|
||||||
animID = (UUID)field.GetValue(null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Failure("Invalid animation name or UUID");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GetClient().Self.AnimationStart(animID, true);
|
|
||||||
return BasicReply("Accepted", [animation]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetGestureTrigger(InventoryItem itm)
|
|
||||||
{
|
|
||||||
LoadGestureCache();
|
|
||||||
lock (gestureTriggerCache)
|
|
||||||
{
|
|
||||||
if (gestureTriggerCache.TryGetValue(itm.AssetUUID, out string cachedTrigger))
|
|
||||||
{
|
|
||||||
return cachedTrigger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetGesture gesture = null;
|
|
||||||
System.Threading.AutoResetEvent gotAsset = new System.Threading.AutoResetEvent(false);
|
|
||||||
|
|
||||||
GetClient().Assets.RequestAsset(itm.AssetUUID, AssetType.Gesture, true,
|
|
||||||
delegate(AssetDownload transfer, Asset asset)
|
|
||||||
{
|
|
||||||
if (transfer.Success)
|
|
||||||
{
|
|
||||||
gesture = (AssetGesture)asset;
|
|
||||||
}
|
|
||||||
gotAsset.Set();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait 500ms max (gestures are tiny and take < 50ms usually)
|
|
||||||
gotAsset.WaitOne(TimeSpan.FromMilliseconds(500), false);
|
|
||||||
|
|
||||||
if (gesture != null && gesture.Decode())
|
|
||||||
{
|
|
||||||
string trigger = gesture.Trigger.Trim().ToLowerInvariant();
|
|
||||||
string replaceWith = gesture.ReplaceWith ?? "";
|
|
||||||
lock (gestureTriggerCache)
|
|
||||||
{
|
|
||||||
gestureTriggerCache[itm.AssetUUID] = trigger;
|
|
||||||
gestureReplaceCache[itm.AssetUUID] = replaceWith;
|
|
||||||
}
|
|
||||||
if (!isPreCachingActive)
|
|
||||||
{
|
|
||||||
SaveGestureCache();
|
|
||||||
}
|
|
||||||
return trigger;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartPreCachingGestures()
|
|
||||||
{
|
|
||||||
LoadGestureCache();
|
|
||||||
if (isPreCachingStarted) return;
|
|
||||||
isPreCachingStarted = true;
|
|
||||||
|
|
||||||
System.Threading.ThreadPool.QueueUserWorkItem(delegate
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
List<InventoryItem> allGestures = new List<InventoryItem>();
|
|
||||||
UUID gestureFolder = GetClient().Inventory.FindFolderForType(AssetType.Gesture);
|
|
||||||
if (gestureFolder != UUID.Zero)
|
|
||||||
{
|
|
||||||
List<InventoryBase> contents = GetClient().Inventory.FolderContents(gestureFolder, GetClient().Self.AgentID, false, true, InventorySortOrder.ByName, TimeSpan.FromSeconds(15));
|
|
||||||
if (contents != null)
|
|
||||||
{
|
|
||||||
foreach (InventoryBase item in contents)
|
|
||||||
{
|
|
||||||
if (item is InventoryItem invItem && invItem.InventoryType == InventoryType.Gesture)
|
|
||||||
{
|
|
||||||
allGestures.Add(invItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Inventory store = GetClient().Inventory.GetStore;
|
|
||||||
lock (store.Items)
|
|
||||||
{
|
|
||||||
foreach (var node in store.Items.Values)
|
|
||||||
{
|
|
||||||
if (node.Data is InventoryItem item && item.InventoryType == InventoryType.Gesture)
|
|
||||||
{
|
|
||||||
if (!allGestures.Any(g => g.UUID == item.UUID))
|
|
||||||
{
|
|
||||||
allGestures.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isPreCachingActive = true;
|
|
||||||
foreach (var item in allGestures)
|
|
||||||
{
|
|
||||||
GetGestureTrigger(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogFormater.Warn("Error pre-caching gestures: " + ex.Message);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
isPreCachingActive = false;
|
|
||||||
SaveGestureCache();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private InventoryItem FindGestureInInventory(string search)
|
|
||||||
{
|
|
||||||
StartPreCachingGestures();
|
|
||||||
|
|
||||||
search = search.Trim().ToLowerInvariant();
|
|
||||||
string searchWithSlash = search.StartsWith("/") ? search : "/" + search;
|
|
||||||
string searchWithoutSlash = search.StartsWith("/") ? search.Substring(1) : search;
|
|
||||||
|
|
||||||
List<InventoryItem> allGestures = new List<InventoryItem>();
|
|
||||||
|
|
||||||
// 1. Gather all gestures from default Gestures folder
|
|
||||||
UUID gestureFolder = GetClient().Inventory.FindFolderForType(AssetType.Gesture);
|
|
||||||
if (gestureFolder != UUID.Zero)
|
|
||||||
{
|
|
||||||
List<InventoryBase> contents = GetClient().Inventory.FolderContents(gestureFolder, GetClient().Self.AgentID, false, true, InventorySortOrder.ByName, TimeSpan.FromSeconds(15));
|
|
||||||
if (contents != null)
|
|
||||||
{
|
|
||||||
foreach (InventoryBase item in contents)
|
|
||||||
{
|
|
||||||
if (item is InventoryItem invItem && invItem.InventoryType == InventoryType.Gesture)
|
|
||||||
{
|
|
||||||
allGestures.Add(invItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Gather from general inventory store cache
|
|
||||||
Inventory store = GetClient().Inventory.GetStore;
|
|
||||||
lock (store.Items)
|
|
||||||
{
|
|
||||||
foreach (var node in store.Items.Values)
|
|
||||||
{
|
|
||||||
if (node.Data is InventoryItem item && item.InventoryType == InventoryType.Gesture)
|
|
||||||
{
|
|
||||||
if (!allGestures.Any(g => g.UUID == item.UUID))
|
|
||||||
{
|
|
||||||
allGestures.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Step A: Search for exact name match first (super fast, no asset download)
|
|
||||||
foreach (var item in allGestures)
|
|
||||||
{
|
|
||||||
string itemName = item.Name.Trim().ToLowerInvariant();
|
|
||||||
if (itemName == searchWithSlash || itemName == searchWithoutSlash)
|
|
||||||
{
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Step B: Search for trigger match (downloads/decodes asset, then checks cache)
|
|
||||||
InventoryItem bestMatch = null;
|
|
||||||
int bestScore = -9999;
|
|
||||||
|
|
||||||
string botFirstName = GetClient().Self.FirstName.ToLowerInvariant();
|
|
||||||
bool isFemaleBot = botFirstName.Contains("girl") || botFirstName.Contains("female") || botFirstName.Contains("lady") || botFirstName.Contains("miss") || botFirstName.Contains("woman") || botFirstName.Contains("queen") || botFirstName.Contains("princess") || botFirstName.Contains("she");
|
|
||||||
bool isMaleBot = botFirstName.Contains("boy") || botFirstName.Contains("male") || botFirstName.Contains("guy") || botFirstName.Contains("man") || botFirstName.Contains("king") || botFirstName.Contains("prince") || botFirstName.Contains("he");
|
|
||||||
|
|
||||||
foreach (var item in allGestures)
|
|
||||||
{
|
|
||||||
string trigger = GetGestureTrigger(item);
|
|
||||||
if (trigger != null)
|
|
||||||
{
|
|
||||||
string triggerWithSlash = trigger.StartsWith("/") ? trigger : "/" + trigger;
|
|
||||||
string triggerWithoutSlash = trigger.StartsWith("/") ? trigger.Substring(1) : trigger;
|
|
||||||
if (trigger == search || triggerWithSlash == search || triggerWithoutSlash == search ||
|
|
||||||
trigger == searchWithSlash || trigger == searchWithoutSlash)
|
|
||||||
{
|
|
||||||
int score = 0;
|
|
||||||
string itemName = item.Name.ToLowerInvariant();
|
|
||||||
|
|
||||||
if (isFemaleBot)
|
|
||||||
{
|
|
||||||
if (itemName.Contains("female") || itemName.Contains("girl") || itemName.Contains("woman") || itemName.Contains("lady") || itemName.Contains("she"))
|
|
||||||
score += 10;
|
|
||||||
if (itemName.Contains("male") || itemName.Contains("boy") || itemName.Contains("man") || itemName.Contains("guy") || itemName.Contains("he"))
|
|
||||||
score -= 10;
|
|
||||||
}
|
|
||||||
else if (isMaleBot)
|
|
||||||
{
|
|
||||||
if (itemName.Contains("male") || itemName.Contains("boy") || itemName.Contains("man") || itemName.Contains("guy") || itemName.Contains("he"))
|
|
||||||
score += 10;
|
|
||||||
if (itemName.Contains("female") || itemName.Contains("girl") || itemName.Contains("woman") || itemName.Contains("lady") || itemName.Contains("she"))
|
|
||||||
score -= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (score > bestScore)
|
|
||||||
{
|
|
||||||
bestScore = score;
|
|
||||||
bestMatch = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestMatch != null)
|
|
||||||
{
|
|
||||||
return bestMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Plays a gesture by name from the inventory")]
|
|
||||||
[ArgHints("gesture", "name of the gesture to play", "STRING")]
|
|
||||||
[CmdTypeDo()]
|
|
||||||
public object PlayGestureByName(string gesture)
|
|
||||||
{
|
|
||||||
string search = gesture.Trim().ToLowerInvariant();
|
|
||||||
|
|
||||||
// First try to match the exact string (like "clap" or ":-D")
|
|
||||||
InventoryItem foundGesture = FindGestureInInventory(search);
|
|
||||||
|
|
||||||
// If no exact match, try splitting into words (natural speak matching, like "i am a sad :-(")
|
|
||||||
if (foundGesture == null)
|
|
||||||
{
|
|
||||||
string[] words = search.Split(new[] { ' ', ',', '.', '!', '?' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
foreach (string word in words)
|
|
||||||
{
|
|
||||||
foundGesture = FindGestureInInventory(word);
|
|
||||||
if (foundGesture != null)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundGesture == null)
|
|
||||||
{
|
|
||||||
return Failure("Gesture not found in inventory");
|
|
||||||
}
|
|
||||||
|
|
||||||
GetClient().Self.ActivateGesture(foundGesture.UUID, foundGesture.AssetUUID);
|
|
||||||
GetClient().Self.PlayGesture(foundGesture.AssetUUID);
|
|
||||||
|
|
||||||
string replaceWith = "";
|
|
||||||
lock (gestureTriggerCache)
|
|
||||||
{
|
|
||||||
gestureReplaceCache.TryGetValue(foundGesture.AssetUUID, out replaceWith);
|
|
||||||
}
|
|
||||||
|
|
||||||
master.DataStoreService.AddCommandToHistory(true, "PlayGestureByName", [gesture]);
|
|
||||||
return new
|
|
||||||
{
|
|
||||||
command = "PlayGestureByName",
|
|
||||||
args = new[] { gesture },
|
|
||||||
reply = "Accepted",
|
|
||||||
status = true,
|
|
||||||
replace_with = replaceWith ?? "",
|
|
||||||
matched_trigger = GetGestureTrigger(foundGesture) ?? ""
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[About("Activates a gesture in the bot's inventory")]
|
|
||||||
[ArgHints("gesture", "inventory uuid of the gesture", "UUID")]
|
|
||||||
[CmdTypeDo()]
|
|
||||||
public object ActivateGesture(string gesture)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(gesture, out UUID gestureUUID) == false) return Failure("Invaild UUID");
|
|
||||||
InventoryItem itm = GetClient().Inventory.FetchItem(gestureUUID, GetClient().Self.AgentID, TimeSpan.FromSeconds(15));
|
|
||||||
if (itm == null) return Failure("unable to get gesture from inventory");
|
|
||||||
GetClient().Self.ActivateGesture(itm.UUID, itm.AssetUUID);
|
|
||||||
return BasicReply("Ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Deactivates a gesture in the bot's inventory")]
|
|
||||||
[ArgHints("gesture", "inventory uuid of the gesture", "UUID")]
|
|
||||||
[CmdTypeDo()]
|
|
||||||
public object DeactivateGesture(string gesture)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(gesture, out UUID gestureUUID) == false) return Failure("Invaild UUID");
|
|
||||||
GetClient().Self.DeactivateGesture(gestureUUID);
|
|
||||||
return BasicReply("Ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Clears the saved gesture cache and runs pre-caching again to pick up new or changed gestures")]
|
|
||||||
[CmdTypeDo()]
|
|
||||||
public object RecacheGestures()
|
|
||||||
{
|
|
||||||
lock (cacheLock)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (System.IO.File.Exists(cacheFilePath))
|
|
||||||
{
|
|
||||||
System.IO.File.Delete(cacheFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {}
|
|
||||||
|
|
||||||
lock (gestureTriggerCache)
|
|
||||||
{
|
|
||||||
gestureTriggerCache.Clear();
|
|
||||||
gestureReplaceCache.Clear();
|
|
||||||
}
|
|
||||||
isCacheLoaded = false;
|
|
||||||
isPreCachingStarted = false;
|
|
||||||
}
|
|
||||||
StartPreCachingGestures();
|
|
||||||
return BasicReply("Recaching started");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Resets the animation stack for the bot")]
|
[About("Resets the animation stack for the bot")]
|
||||||
[ReturnHints("Accepted - X stopped animations")]
|
[ReturnHints("Accepted - X stopped animations")]
|
||||||
[CmdTypeDo()]
|
[CmdTypeDo()]
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,6 @@ namespace SecondBotEvents.Commands
|
||||||
Avatar.AvatarProperties props = reply.Value;
|
Avatar.AvatarProperties props = reply.Value;
|
||||||
props.ProfileImage = textureUUID;
|
props.ProfileImage = textureUUID;
|
||||||
GetClient().Self.UpdateProfile(props);
|
GetClient().Self.UpdateProfile(props);
|
||||||
master.DataStoreService.UpdateAvatarProperties(GetClient().Self.AgentID, props);
|
|
||||||
return BasicReply("ok");
|
return BasicReply("ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +230,7 @@ namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
return Failure("Invaild avatar uuid", [avatar]);
|
return Failure("Invaild avatar uuid", [avatar]);
|
||||||
}
|
}
|
||||||
KeyValuePair<bool, Dictionary<UUID, ProfilePick>> reply = master.DataStoreService.GetAvatarPicksDetailed(avataruuid);
|
KeyValuePair<bool, Dictionary<UUID, string>> reply = master.DataStoreService.GetAvatarPicks(avataruuid);
|
||||||
if (reply.Key == false)
|
if (reply.Key == false)
|
||||||
{
|
{
|
||||||
return Failure("Requesting avatar details [Retry later]", [avatar]);
|
return Failure("Requesting avatar details [Retry later]", [avatar]);
|
||||||
|
|
@ -260,7 +259,7 @@ namespace SecondBotEvents.Commands
|
||||||
[ReturnHints("Requesting avatar details [Retry later]")]
|
[ReturnHints("Requesting avatar details [Retry later]")]
|
||||||
[ReturnHintsFailure("about text to short")]
|
[ReturnHintsFailure("about text to short")]
|
||||||
[ReturnHintsFailure("about text to long")]
|
[ReturnHintsFailure("about text to long")]
|
||||||
[ArgHints("abouttext", "text to use in the about me area, length 10 to 510 bytes", "Text", "Hello world!")]
|
[ArgHints("abouttext", "text to use in the about me area, length 10 to 300", "Text", "Hello world!")]
|
||||||
[CmdTypeSet()]
|
[CmdTypeSet()]
|
||||||
public object SetProfileAbout(string abouttext)
|
public object SetProfileAbout(string abouttext)
|
||||||
{
|
{
|
||||||
|
|
@ -268,7 +267,7 @@ namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
return Failure("About text to short", [abouttext]);
|
return Failure("About text to short", [abouttext]);
|
||||||
}
|
}
|
||||||
else if (System.Text.Encoding.UTF8.GetByteCount(abouttext) > 510)
|
else if (abouttext.Length > 300)
|
||||||
{
|
{
|
||||||
return Failure("About text to long", [abouttext]);
|
return Failure("About text to long", [abouttext]);
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +279,6 @@ namespace SecondBotEvents.Commands
|
||||||
Avatar.AvatarProperties props = reply.Value;
|
Avatar.AvatarProperties props = reply.Value;
|
||||||
props.AboutText = abouttext;
|
props.AboutText = abouttext;
|
||||||
GetClient().Self.UpdateProfile(props);
|
GetClient().Self.UpdateProfile(props);
|
||||||
master.DataStoreService.UpdateAvatarProperties(GetClient().Self.AgentID, props);
|
|
||||||
return BasicReply("ok");
|
return BasicReply("ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,11 +314,11 @@ namespace SecondBotEvents.Commands
|
||||||
[CmdTypeDo()]
|
[CmdTypeDo()]
|
||||||
public object Key2Name(string uuid)
|
public object Key2Name(string uuid)
|
||||||
{
|
{
|
||||||
if (UUID.TryParse(uuid, out UUID parsedUUID) == false)
|
if (UUID.TryParse(uuid, out _) == false)
|
||||||
{
|
{
|
||||||
return Failure("Not a vaild UUID");
|
return Failure("Not a vaild UUID");
|
||||||
}
|
}
|
||||||
return BasicReply(master.DataStoreService.GetAvatarName(parsedUUID));
|
return BasicReply(master.DataStoreService.GetAvatarName(UUID.Zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("searchs the AV database if not found triggers a lookup")]
|
[About("searchs the AV database if not found triggers a lookup")]
|
||||||
|
|
@ -337,187 +335,5 @@ namespace SecondBotEvents.Commands
|
||||||
}
|
}
|
||||||
return BasicReply(master.DataStoreService.GetAvatarUUID(name));
|
return BasicReply(master.DataStoreService.GetAvatarUUID(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Sets the bot display name")]
|
|
||||||
[CmdTypeSet()]
|
|
||||||
public object SetDisplayName(string newname)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(newname))
|
|
||||||
{
|
|
||||||
return Failure("Display name cannot be empty");
|
|
||||||
}
|
|
||||||
GetClient().Self.SetDisplayName(GetClient().Self.Name, newname);
|
|
||||||
return BasicReply("ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Sets the bot first life profile text and image")]
|
|
||||||
[CmdTypeSet()]
|
|
||||||
public object SetFirstLife(string text, string image)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(image, out UUID imageUUID) == false)
|
|
||||||
{
|
|
||||||
return Failure("Invalid image uuid");
|
|
||||||
}
|
|
||||||
KeyValuePair<bool, Avatar.AvatarProperties> reply = master.DataStoreService.GetAvatarAvatarProperties(GetClient().Self.AgentID);
|
|
||||||
if (reply.Key == false)
|
|
||||||
{
|
|
||||||
return Failure("Requesting avatar details [Retry later]");
|
|
||||||
}
|
|
||||||
Avatar.AvatarProperties props = reply.Value;
|
|
||||||
props.FirstLifeText = text;
|
|
||||||
props.FirstLifeImage = imageUUID;
|
|
||||||
GetClient().Self.UpdateProfile(props);
|
|
||||||
master.DataStoreService.UpdateAvatarProperties(GetClient().Self.AgentID, props);
|
|
||||||
return BasicReply("ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Creates or updates a profile pick")]
|
|
||||||
[CmdTypeSet()]
|
|
||||||
public object SetAvatarPick(string id, string name, string desc, string snapshot, string sim, string pos)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(id, out UUID pickUUID) == false)
|
|
||||||
{
|
|
||||||
return Failure("Invalid pick uuid");
|
|
||||||
}
|
|
||||||
if (UUID.TryParse(snapshot, out UUID snapshotUUID) == false)
|
|
||||||
{
|
|
||||||
snapshotUUID = UUID.Zero;
|
|
||||||
}
|
|
||||||
Vector3 position = Vector3.Zero;
|
|
||||||
if (Vector3.TryParse(pos, out Vector3 parsedPos))
|
|
||||||
{
|
|
||||||
position = parsedPos;
|
|
||||||
}
|
|
||||||
Vector3d globalPos = GetClient().Self.GlobalPosition;
|
|
||||||
if (!string.IsNullOrWhiteSpace(sim) && sim == GetClient().Network.CurrentSim.Name)
|
|
||||||
{
|
|
||||||
Utils.LongToUInts(GetClient().Network.CurrentSim.Handle, out uint x, out uint y);
|
|
||||||
globalPos = new Vector3d(x + position.X, y + position.Y, position.Z);
|
|
||||||
}
|
|
||||||
GetClient().Self.PickInfoUpdate(pickUUID, false, UUID.Zero, name, globalPos, snapshotUUID, desc);
|
|
||||||
master.DataStoreService.ClearAvatarPicksCache(GetClient().Self.AgentID);
|
|
||||||
return BasicReply("ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Deletes a profile pick")]
|
|
||||||
[CmdTypeSet()]
|
|
||||||
public object DeleteAvatarPick(string id)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(id, out UUID pickUUID) == false)
|
|
||||||
{
|
|
||||||
return Failure("Invalid pick uuid");
|
|
||||||
}
|
|
||||||
GetClient().Self.PickDelete(pickUUID);
|
|
||||||
master.DataStoreService.ClearAvatarPicksCache(GetClient().Self.AgentID);
|
|
||||||
return BasicReply("ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Creates or updates a classified ad")]
|
|
||||||
[CmdTypeSet()]
|
|
||||||
public object SetAvatarClassified(string id, string name, string desc, string snapshot, string category, string price, string autorenew)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(id, out UUID classifiedUUID) == false)
|
|
||||||
{
|
|
||||||
return Failure("Invalid classified uuid");
|
|
||||||
}
|
|
||||||
if (UUID.TryParse(snapshot, out UUID snapshotUUID) == false)
|
|
||||||
{
|
|
||||||
snapshotUUID = UUID.Zero;
|
|
||||||
}
|
|
||||||
int adPrice = 50;
|
|
||||||
int.TryParse(price, out adPrice);
|
|
||||||
bool renew = autorenew.ToLower() == "true";
|
|
||||||
|
|
||||||
DirectoryManager.ClassifiedCategories cat = DirectoryManager.ClassifiedCategories.Shopping;
|
|
||||||
if (Enum.TryParse(category, out DirectoryManager.ClassifiedCategories parsedCat))
|
|
||||||
{
|
|
||||||
cat = parsedCat;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetClient().Self.UpdateClassifiedInfo(classifiedUUID, cat, snapshotUUID, adPrice, name, desc, renew);
|
|
||||||
master.DataStoreService.ClearAvatarClassifiedsCache(GetClient().Self.AgentID);
|
|
||||||
return BasicReply("ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Deletes a classified ad")]
|
|
||||||
[CmdTypeSet()]
|
|
||||||
public object DeleteAvatarClassified(string id)
|
|
||||||
{
|
|
||||||
if (UUID.TryParse(id, out UUID classifiedUUID) == false)
|
|
||||||
{
|
|
||||||
return Failure("Invalid classified uuid");
|
|
||||||
}
|
|
||||||
GetClient().Self.DeleteClassified(classifiedUUID);
|
|
||||||
master.DataStoreService.ClearAvatarClassifiedsCache(GetClient().Self.AgentID);
|
|
||||||
return BasicReply("ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Requests the given avatars profile classifieds")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetAvatarClassifieds(string avatar)
|
|
||||||
{
|
|
||||||
ProcessAvatar(avatar);
|
|
||||||
if (avataruuid == UUID.Zero)
|
|
||||||
{
|
|
||||||
return Failure("Invaild avatar uuid", [avatar]);
|
|
||||||
}
|
|
||||||
KeyValuePair<bool, Dictionary<UUID, ClassifiedAd>> reply = master.DataStoreService.GetAvatarClassifiedsDetailed(avataruuid);
|
|
||||||
if (reply.Key == false)
|
|
||||||
{
|
|
||||||
return Failure("Requesting avatar details [Retry later]", [avatar]);
|
|
||||||
}
|
|
||||||
return BasicReply(JsonSerializer.Serialize(reply.Value, JsonOptions.UnsafeRelaxed));
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Requests the given avatars first life profile text")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetFirstLifeText(string avatar)
|
|
||||||
{
|
|
||||||
ProcessAvatar(avatar);
|
|
||||||
if (avataruuid == UUID.Zero)
|
|
||||||
{
|
|
||||||
return Failure("Invaild avatar uuid", [avatar]);
|
|
||||||
}
|
|
||||||
KeyValuePair<bool, Avatar.AvatarProperties> reply = master.DataStoreService.GetAvatarAvatarProperties(avataruuid);
|
|
||||||
if (reply.Key == false)
|
|
||||||
{
|
|
||||||
return Failure("Requesting avatar details [Retry later]", [avatar]);
|
|
||||||
}
|
|
||||||
return BasicReply(reply.Value.FirstLifeText.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Requests the given avatars first life profile image")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetFirstLifeImage(string avatar)
|
|
||||||
{
|
|
||||||
ProcessAvatar(avatar);
|
|
||||||
if (avataruuid == UUID.Zero)
|
|
||||||
{
|
|
||||||
return Failure("Invaild avatar uuid", [avatar]);
|
|
||||||
}
|
|
||||||
KeyValuePair<bool, Avatar.AvatarProperties> reply = master.DataStoreService.GetAvatarAvatarProperties(avataruuid);
|
|
||||||
if (reply.Key == false)
|
|
||||||
{
|
|
||||||
return Failure("Requesting avatar details [Retry later]", [avatar]);
|
|
||||||
}
|
|
||||||
return BasicReply(reply.Value.FirstLifeImage.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Requests the given avatars profile groups")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetAvatarGroups(string avatar)
|
|
||||||
{
|
|
||||||
ProcessAvatar(avatar);
|
|
||||||
if (avataruuid == UUID.Zero)
|
|
||||||
{
|
|
||||||
return Failure("Invaild avatar uuid", [avatar]);
|
|
||||||
}
|
|
||||||
KeyValuePair<bool, List<AvatarGroup>> reply = master.DataStoreService.GetAvatarGroups(avataruuid);
|
|
||||||
if (reply.Key == false)
|
|
||||||
{
|
|
||||||
return Failure("Requesting avatar details [Retry later]", [avatar]);
|
|
||||||
}
|
|
||||||
return BasicReply(JsonSerializer.Serialize(reply.Value, JsonOptions.UnsafeRelaxed));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ namespace SecondBotEvents.Commands
|
||||||
return Failure("Invaild channel", [channel, message]);
|
return Failure("Invaild channel", [channel, message]);
|
||||||
}
|
}
|
||||||
string typeTag = (channelnum == 0) ? "" : $"[Channel {channelnum}] ";
|
string typeTag = (channelnum == 0) ? "" : $"[Channel {channelnum}] ";
|
||||||
|
master.DataStoreService.BotRecordLocalchatReply(message, typeTag);
|
||||||
GetClient().Self.Chat(message, channelnum, ChatType.Normal);
|
GetClient().Self.Chat(message, channelnum, ChatType.Normal);
|
||||||
return BasicReply("ok");
|
return BasicReply("ok");
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +53,7 @@ namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
return Failure("Invaild channel", [channel, message]);
|
return Failure("Invaild channel", [channel, message]);
|
||||||
}
|
}
|
||||||
|
master.DataStoreService.BotRecordLocalchatReply(message, "[Shout] ");
|
||||||
GetClient().Self.Chat(message, channelnum, ChatType.Shout);
|
GetClient().Self.Chat(message, channelnum, ChatType.Shout);
|
||||||
return BasicReply("ok");
|
return BasicReply("ok");
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +74,7 @@ namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
return Failure("Invaild channel", [channel, message]);
|
return Failure("Invaild channel", [channel, message]);
|
||||||
}
|
}
|
||||||
|
master.DataStoreService.BotRecordLocalchatReply(message, "[Whisper] ");
|
||||||
GetClient().Self.Chat(message, channelnum, ChatType.Whisper);
|
GetClient().Self.Chat(message, channelnum, ChatType.Whisper);
|
||||||
return BasicReply("ok");
|
return BasicReply("ok");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,6 @@ namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
return BasicReply(GetClient().Self.Name);
|
return BasicReply(GetClient().Self.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Returns the bot's UUID")]
|
|
||||||
[ReturnHints("Bot UUID")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetBotUUID()
|
|
||||||
{
|
|
||||||
return BasicReply(GetClient().Self.AgentID.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CommandLibCall
|
public class CommandLibCall
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using SecondBotEvents.Services;
|
using SecondBotEvents.Services;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
|
|
||||||
namespace SecondBotEvents.Commands
|
namespace SecondBotEvents.Commands
|
||||||
|
|
@ -86,13 +84,5 @@ namespace SecondBotEvents.Commands
|
||||||
return BasicReply(master.DialogService.DialogAction(dialogidnum, buttontext));
|
return BasicReply(master.DialogService.DialogAction(dialogidnum, buttontext));
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Lists all active dialogs currently waiting for a response")]
|
|
||||||
[ReturnHints("A JSON array of active dialogs")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetDialogs()
|
|
||||||
{
|
|
||||||
var dialogs = master.DialogService.GetActiveDialogs();
|
|
||||||
return BasicReply(JsonSerializer.Serialize(dialogs, JsonOptions.UnsafeRelaxed));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,48 +44,21 @@ namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
Dictionary<UUID, FriendInfo> FriendListCopy = GetClient().Friends.FriendList.ToDictionary(k => k.Key, v => v.Value);
|
Dictionary<UUID, FriendInfo> FriendListCopy = GetClient().Friends.FriendList.ToDictionary(k => k.Key, v => v.Value);
|
||||||
List< FriendListEntry > CleanedFriendsList = [];
|
List< FriendListEntry > CleanedFriendsList = [];
|
||||||
|
int index = 0;
|
||||||
foreach (FriendInfo A in FriendListCopy.Values)
|
foreach (FriendInfo A in FriendListCopy.Values)
|
||||||
{
|
{
|
||||||
FriendListEntry entry = new()
|
FriendListEntry entry = new()
|
||||||
{
|
{
|
||||||
id = A.UUID.ToString(),
|
id = A.UUID.ToString(),
|
||||||
name = A.Name,
|
name = A.Name,
|
||||||
online = A.IsOnline,
|
online = A.IsOnline
|
||||||
canSeeMeOnline = A.CanSeeMeOnline,
|
|
||||||
canSeeMeOnMap = A.CanSeeMeOnMap,
|
|
||||||
canModifyMyObjects = A.CanModifyMyObjects,
|
|
||||||
canSeeThemOnline = A.CanSeeThemOnline,
|
|
||||||
canSeeThemOnMap = A.CanSeeThemOnMap,
|
|
||||||
canModifyTheirObjects = A.CanModifyTheirObjects
|
|
||||||
};
|
};
|
||||||
CleanedFriendsList.Add(entry);
|
CleanedFriendsList.Add(entry);
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
return BasicReply(JsonSerializer.Serialize(CleanedFriendsList, JsonOptions.UnsafeRelaxed));
|
return BasicReply(JsonSerializer.Serialize(CleanedFriendsList, JsonOptions.UnsafeRelaxed));
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Updates specific rights granted to a friend")]
|
|
||||||
[ArgHints("avatar", "The avatar to change rights for","AVATAR")]
|
|
||||||
[ArgHints("rights", "The exact integer mask of FriendRights to grant","INT")]
|
|
||||||
[CmdTypeSet()]
|
|
||||||
public object FriendSetRights(string avatar, string rights)
|
|
||||||
{
|
|
||||||
ProcessAvatar(avatar);
|
|
||||||
if (avataruuid == UUID.Zero)
|
|
||||||
{
|
|
||||||
return Failure("avatar lookup", [avatar, rights]);
|
|
||||||
}
|
|
||||||
if (int.TryParse(rights, out int mask) == false)
|
|
||||||
{
|
|
||||||
return Failure("invalid rights mask", [avatar, rights]);
|
|
||||||
}
|
|
||||||
if (GetClient().Friends.FriendList.ContainsKey(avataruuid) == false)
|
|
||||||
{
|
|
||||||
return Failure("Not A friend", [avatar, rights]);
|
|
||||||
}
|
|
||||||
GetClient().Friends.GrantRights(avataruuid, (FriendRights)mask);
|
|
||||||
return BasicReply("rights_updated", [avatar, rights]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Updates the friend perms for avatar avatar to State \n if true grants (Online/Map/Modify) perms")]
|
[About("Updates the friend perms for avatar avatar to State \n if true grants (Online/Map/Modify) perms")]
|
||||||
[ArgHints("avatar", "Who we are talking about","AVATAR")]
|
[ArgHints("avatar", "Who we are talking about","AVATAR")]
|
||||||
[ArgHints("state", "true: Grant perms, false: Remove perms","BOOL")]
|
[ArgHints("state", "true: Grant perms, false: Remove perms","BOOL")]
|
||||||
|
|
@ -213,11 +186,5 @@ namespace SecondBotEvents.Commands
|
||||||
public string name;
|
public string name;
|
||||||
public string id;
|
public string id;
|
||||||
public bool online = false;
|
public bool online = false;
|
||||||
public bool canSeeMeOnline = false;
|
|
||||||
public bool canSeeMeOnMap = false;
|
|
||||||
public bool canModifyMyObjects = false;
|
|
||||||
public bool canSeeThemOnline = false;
|
|
||||||
public bool canSeeThemOnMap = false;
|
|
||||||
public bool canModifyTheirObjects = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ using System.Text.Json;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using SecondBotEvents.Services;
|
using SecondBotEvents.Services;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace SecondBotEvents.Commands
|
namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
|
|
@ -355,63 +354,24 @@ namespace SecondBotEvents.Commands
|
||||||
return BasicReply("Working on it");
|
return BasicReply("Working on it");
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Returns a list of roles for a group")]
|
[About("Requests the roles for the selected group")]
|
||||||
[ReturnHints("dictionary uuid:name")]
|
[ReturnHints("GroupRoleDetails object")]
|
||||||
|
[ReturnHintsFailure("Group is not currently known")]
|
||||||
[ReturnHintsFailure("Invaild group UUID")]
|
[ReturnHintsFailure("Invaild group UUID")]
|
||||||
[ReturnHintsFailure("Updating")]
|
[ReturnHintsFailure("Updating")]
|
||||||
[ArgHints("group", "the group to get roles from", "UUID")]
|
[ArgHints("group", "the group to get roles from", "UUID")]
|
||||||
[ArgHints("botOnly", "only return roles the bot is in", "bool")]
|
|
||||||
[ArgHints("force", "force refresh from server", "bool")]
|
|
||||||
[CmdTypeGet()]
|
[CmdTypeGet()]
|
||||||
public object GetGroupRoles(string group, string botOnly = "false", string force = "false")
|
public object GetGroupRoles(string group)
|
||||||
{
|
{
|
||||||
if (UUID.TryParse(group, out UUID groupuuid) == false)
|
if (UUID.TryParse(group, out UUID groupuuid) == false)
|
||||||
{
|
{
|
||||||
return Failure("Invaild group UUID", [group]);
|
return Failure("Invaild group UUID", [group]);
|
||||||
}
|
}
|
||||||
|
if (GetClient().Groups.GroupName2KeyCache.ContainsKey(groupuuid) == false)
|
||||||
bool doForce = force.ToLower() == "true";
|
|
||||||
Dictionary<UUID, string> allRoles = master.DataStoreService.GetGroupRoles(groupuuid, doForce);
|
|
||||||
|
|
||||||
if (botOnly.ToLower() == "true")
|
|
||||||
{
|
{
|
||||||
Dictionary<UUID, string> botRoles = [];
|
return Failure("Unknown group", [group]);
|
||||||
AutoResetEvent fetchEvent = new(false);
|
|
||||||
void callback(object sender, GroupRolesMembersReplyEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.GroupID == groupuuid)
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<UUID, UUID> pair in e.RolesMembers)
|
|
||||||
{
|
|
||||||
if (pair.Value == GetClient().Self.AgentID)
|
|
||||||
{
|
|
||||||
if (allRoles.ContainsKey(pair.Key))
|
|
||||||
{
|
|
||||||
botRoles[pair.Key] = allRoles[pair.Key];
|
|
||||||
}
|
}
|
||||||
}
|
return BasicReply(JsonSerializer.Serialize(master.DataStoreService.GetGroupRoles(groupuuid), JsonOptions.UnsafeRelaxed));
|
||||||
}
|
|
||||||
fetchEvent.Set();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GetClient().Groups.GroupRoleMembersReply += callback;
|
|
||||||
GetClient().Groups.RequestGroupRolesMembers(groupuuid);
|
|
||||||
fetchEvent.WaitOne(2000, false);
|
|
||||||
GetClient().Groups.GroupRoleMembersReply -= callback;
|
|
||||||
|
|
||||||
// Ensure 'Everyone' role is included if we missed it
|
|
||||||
foreach(var role in allRoles)
|
|
||||||
{
|
|
||||||
if (role.Value.ToLower() == "everyone" || role.Key == UUID.Zero)
|
|
||||||
{
|
|
||||||
if (!botRoles.ContainsKey(role.Key)) botRoles[role.Key] = role.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BasicReply(JsonSerializer.Serialize(botRoles, JsonOptions.UnsafeRelaxed));
|
|
||||||
}
|
|
||||||
|
|
||||||
return BasicReply(JsonSerializer.Serialize(allRoles, JsonOptions.UnsafeRelaxed));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("fetchs a list of all groups with unread messages")]
|
[About("fetchs a list of all groups with unread messages")]
|
||||||
|
|
@ -495,6 +455,7 @@ namespace SecondBotEvents.Commands
|
||||||
return Failure("Unknown group", [group]);
|
return Failure("Unknown group", [group]);
|
||||||
}
|
}
|
||||||
GetClient().Self.InstantMessageGroup(groupUUID, message);
|
GetClient().Self.InstantMessageGroup(groupUUID, message);
|
||||||
|
master.DataStoreService.BotRecordReplyIM(groupUUID, message);
|
||||||
return BasicReply("Sending");
|
return BasicReply("Sending");
|
||||||
}
|
}
|
||||||
[About("fetchs a list of all pending group invitations")]
|
[About("fetchs a list of all pending group invitations")]
|
||||||
|
|
@ -523,12 +484,5 @@ namespace SecondBotEvents.Commands
|
||||||
master.DataStoreService.RemovePendingGroupInvite(groupuuid);
|
master.DataStoreService.RemovePendingGroupInvite(groupuuid);
|
||||||
return BasicReply("Done");
|
return BasicReply("Done");
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Gets the currently active group and title")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetGroupStatus()
|
|
||||||
{
|
|
||||||
return BasicReply(GetClient().Self.ActiveGroup.ToString(), [master.DataStoreService.GetActiveGroupTitle(), master.DataStoreService.GetGroupName(GetClient().Self.ActiveGroup)]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,22 +179,11 @@ namespace SecondBotEvents.Commands
|
||||||
{
|
{
|
||||||
return Failure("Error not in a sim");
|
return Failure("Error not in a sim");
|
||||||
}
|
}
|
||||||
uint sittingOn = GetClient().Self.SittingOn;
|
Dictionary<string, int> pos = new()
|
||||||
string sittingOnUUID = "00000000-0000-0000-0000-000000000000";
|
|
||||||
if (sittingOn != 0)
|
|
||||||
{
|
|
||||||
if (GetClient().Network.CurrentSim.ObjectsPrimitives.TryGetValue(sittingOn, out Primitive prim))
|
|
||||||
{
|
|
||||||
sittingOnUUID = prim.ID.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<string, object> pos = new()
|
|
||||||
{
|
{
|
||||||
{ "x", (int)Math.Round(GetClient().Self.SimPosition.X) },
|
{ "x", (int)Math.Round(GetClient().Self.SimPosition.X) },
|
||||||
{ "y", (int)Math.Round(GetClient().Self.SimPosition.Y) },
|
{ "y", (int)Math.Round(GetClient().Self.SimPosition.Y) },
|
||||||
{ "z", (int)Math.Round(GetClient().Self.SimPosition.Z) },
|
{ "z", (int)Math.Round(GetClient().Self.SimPosition.Z) }
|
||||||
{ "sitting_on", sittingOnUUID }
|
|
||||||
};
|
};
|
||||||
return BasicReply(JsonSerializer.Serialize(pos, JsonOptions.UnsafeRelaxed));
|
return BasicReply(JsonSerializer.Serialize(pos, JsonOptions.UnsafeRelaxed));
|
||||||
}
|
}
|
||||||
|
|
@ -211,59 +200,6 @@ namespace SecondBotEvents.Commands
|
||||||
if (rules == null) return BasicReply("[]");
|
if (rules == null) return BasicReply("[]");
|
||||||
return BasicReply(JsonSerializer.Serialize(rules, JsonOptions.UnsafeRelaxed));
|
return BasicReply(JsonSerializer.Serialize(rules, JsonOptions.UnsafeRelaxed));
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Lists objects near the bot (within 20m)")]
|
|
||||||
[ReturnHints("A JSON array of nearby objects")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetNearbyObjects()
|
|
||||||
{
|
|
||||||
if (GetClient().Network.CurrentSim == null) return Failure("Not in a sim");
|
|
||||||
|
|
||||||
Vector3 botPos = GetClient().Self.SimPosition;
|
|
||||||
uint sittingOn = GetClient().Self.SittingOn;
|
|
||||||
List<uint> localIDsToRequest = [];
|
|
||||||
|
|
||||||
// First pass: Find objects and collect localIDs for properties we don't have
|
|
||||||
foreach (var prim in GetClient().Network.CurrentSim.ObjectsPrimitives.Values)
|
|
||||||
{
|
|
||||||
if (prim.ParentID != 0) continue;
|
|
||||||
if (Vector3.Distance(botPos, prim.Position) <= 20.0f)
|
|
||||||
{
|
|
||||||
if (prim.Properties == null) localIDsToRequest.Add(prim.LocalID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have missing names, request them and wait a short moment
|
|
||||||
if (localIDsToRequest.Count > 0)
|
|
||||||
{
|
|
||||||
GetClient().Objects.SelectObjects(GetClient().Network.CurrentSim, localIDsToRequest.ToArray());
|
|
||||||
Thread.Sleep(500); // Wait for properties to arrive
|
|
||||||
}
|
|
||||||
|
|
||||||
List<object> reply = [];
|
|
||||||
foreach (var prim in GetClient().Network.CurrentSim.ObjectsPrimitives.Values)
|
|
||||||
{
|
|
||||||
if (prim.ParentID != 0) continue;
|
|
||||||
|
|
||||||
float dist = Vector3.Distance(botPos, prim.Position);
|
|
||||||
if (dist <= 20.0f)
|
|
||||||
{
|
|
||||||
string name = prim.Properties?.Name;
|
|
||||||
if (string.IsNullOrEmpty(name)) name = "Object #" + prim.LocalID;
|
|
||||||
|
|
||||||
reply.Add(new {
|
|
||||||
name = name,
|
|
||||||
uuid = prim.ID.ToString(),
|
|
||||||
local_id = prim.LocalID,
|
|
||||||
dist = Math.Round(dist, 1),
|
|
||||||
pos = prim.Position.ToString(),
|
|
||||||
is_sitting = (prim.LocalID == sittingOn)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BasicReply(JsonSerializer.Serialize(reply, JsonOptions.UnsafeRelaxed));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SculptysInfo
|
public class SculptysInfo
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using OpenMetaverse.ImportExport.Collada14;
|
using OpenMetaverse.ImportExport.Collada14;
|
||||||
using SecondBotEvents.Services;
|
using SecondBotEvents.Services;
|
||||||
|
|
@ -86,21 +86,6 @@ namespace SecondBotEvents.Commands
|
||||||
return BasicReply(test.ToString());
|
return BasicReply(test.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[About("Gets the current parcel's snapshot UUID")]
|
|
||||||
[ReturnHints("snapshot uuid")]
|
|
||||||
[ReturnHintsFailure("Error not in a sim")]
|
|
||||||
[ReturnHintsFailure("Parcel data not ready")]
|
|
||||||
[CmdTypeGet()]
|
|
||||||
public object GetCurrentParcelSnapshot()
|
|
||||||
{
|
|
||||||
KeyValuePair<bool, string> tests = SetupCurrentParcel();
|
|
||||||
if (tests.Key == false)
|
|
||||||
{
|
|
||||||
return Failure(tests.Value);
|
|
||||||
}
|
|
||||||
return BasicReply(targetparcel.SnapshotID.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[About("Requests the bot update its list of parcels ready for other commands")]
|
[About("Requests the bot update its list of parcels ready for other commands")]
|
||||||
[ReturnHints("ok")]
|
[ReturnHints("ok")]
|
||||||
[CmdTypeDo()]
|
[CmdTypeDo()]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
@ -14,16 +14,6 @@ namespace SecondBotEvents.Config
|
||||||
settings.Add("LoginURI");
|
settings.Add("LoginURI");
|
||||||
settings.Add("LogCommands");
|
settings.Add("LogCommands");
|
||||||
settings.Add("DefaultHoverHeight");
|
settings.Add("DefaultHoverHeight");
|
||||||
settings.Add("MAC");
|
|
||||||
settings.Add("Id0");
|
|
||||||
settings.Add("StartLocation");
|
|
||||||
settings.Add("ReconnectDelaySecs");
|
|
||||||
settings.Add("MaxReconnectDelaySecs");
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetStartLocation()
|
|
||||||
{
|
|
||||||
return ReadSettingAsString("StartLocation", "last");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double GetDefaultHoverHeight()
|
public double GetDefaultHoverHeight()
|
||||||
|
|
@ -65,33 +55,5 @@ namespace SecondBotEvents.Config
|
||||||
{
|
{
|
||||||
return ReadSettingAsString("LoginURI", "secondlife");
|
return ReadSettingAsString("LoginURI", "secondlife");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetMAC()
|
|
||||||
{
|
|
||||||
return ReadSettingAsString("MAC", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetId0()
|
|
||||||
{
|
|
||||||
return ReadSettingAsString("Id0", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base reconnect delay in seconds. Doubles on each consecutive failure (exponential backoff).
|
|
||||||
/// Default: 120 seconds.
|
|
||||||
/// </summary>
|
|
||||||
public int GetReconnectDelaySecs()
|
|
||||||
{
|
|
||||||
return ReadSettingAsInt("ReconnectDelaySecs", 120);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum reconnect delay cap in seconds for exponential backoff.
|
|
||||||
/// Default: 600 seconds (10 minutes).
|
|
||||||
/// </summary>
|
|
||||||
public int GetMaxReconnectDelaySecs()
|
|
||||||
{
|
|
||||||
return ReadSettingAsInt("MaxReconnectDelaySecs", 600);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace SecondBotEvents.Config
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Configuration for bot reconnection jitter timings.
|
|
||||||
/// </summary>
|
|
||||||
public class BotRejoinConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Minimum jitter (in milliseconds) before a bot attempts to reconnect after a restart.
|
|
||||||
/// </summary>
|
|
||||||
public int JitterMinMs { get; set; } = 5000; // 5 seconds
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum jitter (in milliseconds) before a bot attempts to reconnect after a restart.
|
|
||||||
/// </summary>
|
|
||||||
public int JitterMaxMs { get; set; } = 30000; // 30 seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
@ -10,7 +10,6 @@ namespace SecondBotEvents.Config
|
||||||
{
|
{
|
||||||
filename = "http";
|
filename = "http";
|
||||||
settings.Add("Enabled");
|
settings.Add("Enabled");
|
||||||
settings.Add("Port");
|
|
||||||
settings.Add("HideStatusOutput");
|
settings.Add("HideStatusOutput");
|
||||||
settings.Add("DisableCommandValidation");
|
settings.Add("DisableCommandValidation");
|
||||||
}
|
}
|
||||||
|
|
@ -24,11 +23,6 @@ namespace SecondBotEvents.Config
|
||||||
{
|
{
|
||||||
return ReadSettingAsBool("DisableCommandValidation", false);
|
return ReadSettingAsBool("DisableCommandValidation", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetPort()
|
|
||||||
{
|
|
||||||
return ReadSettingAsInt("Port", 80);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,16 +11,6 @@ namespace SecondBotEvents
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// Force initialize OpenMetaverse logger early to avoid log4net repository registration errors
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var _ = OpenMetaverse.Logger.LogInstance;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"[Startup] Warning: Failed to initialize OpenMetaverse logger: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#if (DEBUG)
|
#if (DEBUG)
|
||||||
new WikiMake();
|
new WikiMake();
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk;Microsoft.NET.Sdk.Publish">
|
<Project Sdk="Microsoft.NET.Sdk;Microsoft.NET.Sdk.Publish">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<StartupObject>SecondBotEvents.Startup</StartupObject>
|
<StartupObject>SecondBotEvents.Startup</StartupObject>
|
||||||
<MSBuildGitHashCommand>git rev-parse --short HEAD</MSBuildGitHashCommand>
|
<MSBuildGitHashCommand>git rev-parse --short HEAD</MSBuildGitHashCommand>
|
||||||
<MSBuildGitHashValidate>False</MSBuildGitHashValidate>
|
<MSBuildGitHashValidate>False</MSBuildGitHashValidate>
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,8 @@ namespace SecondBotEvents.Services
|
||||||
{
|
{
|
||||||
public GridClient client = null;
|
public GridClient client = null;
|
||||||
public BasicConfig basicCfg = null;
|
public BasicConfig basicCfg = null;
|
||||||
private BotRejoinConfig rejoinCfg;
|
|
||||||
|
|
||||||
protected bool LogoutExpected = false;
|
protected bool LogoutExpected = false;
|
||||||
protected int consecutiveLoginFailures = 0;
|
|
||||||
protected DateTime lastConnectTime = DateTime.MinValue;
|
|
||||||
protected bool isRegionRestartScheduled = false;
|
|
||||||
public void flagLogoutExpected()
|
public void flagLogoutExpected()
|
||||||
{
|
{
|
||||||
LogoutExpected = true;
|
LogoutExpected = true;
|
||||||
|
|
@ -44,19 +40,14 @@ private BotRejoinConfig rejoinCfg;
|
||||||
public BotClientService(EventsSecondBot setMaster) : base(setMaster)
|
public BotClientService(EventsSecondBot setMaster) : base(setMaster)
|
||||||
{
|
{
|
||||||
basicCfg = new BasicConfig(master.fromEnv, master.fromFolder);
|
basicCfg = new BasicConfig(master.fromEnv, master.fromFolder);
|
||||||
rejoinCfg = new BotRejoinConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void RestartTimer(object o, ElapsedEventArgs e)
|
protected void RestartTimer(object o, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
AutoRestartLoginTimer?.Stop();
|
AutoRestartLoginTimer.Stop();
|
||||||
AutoRestartLoginTimer = null;
|
AutoRestartLoginTimer = null;
|
||||||
// Ensure logger is initialized early to avoid "logger not registered" errors
|
LogFormater.Info("Recovery underway - waiting 4 secs then restarting bot");
|
||||||
var _ = OpenMetaverse.Logger.LogInstance;
|
Thread.Sleep(4000);
|
||||||
// Apply jitter before restart
|
|
||||||
int jitter = new Random().Next(rejoinCfg.JitterMinMs, rejoinCfg.JitterMaxMs);
|
|
||||||
LogFormater.Info($"Recovery sequence: Waiting {jitter}ms before restart (jitter applied)...");
|
|
||||||
Thread.Sleep(jitter);
|
|
||||||
Restart();
|
Restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,56 +90,10 @@ private BotRejoinConfig rejoinCfg;
|
||||||
ResetClient();
|
ResetClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void HandleUnexpectedDisconnect(string reason)
|
|
||||||
{
|
|
||||||
if (getLogoutExpected()) return;
|
|
||||||
|
|
||||||
int baseDelay = basicCfg.GetReconnectDelaySecs();
|
|
||||||
int maxDelay = basicCfg.GetMaxReconnectDelaySecs();
|
|
||||||
|
|
||||||
if (lastConnectTime != DateTime.MinValue && (DateTime.UtcNow - lastConnectTime).TotalSeconds > 60)
|
|
||||||
{
|
|
||||||
consecutiveLoginFailures = 0;
|
|
||||||
}
|
|
||||||
lastConnectTime = DateTime.MinValue;
|
|
||||||
|
|
||||||
int delaySecs = (int)Math.Min(baseDelay * Math.Pow(2, consecutiveLoginFailures), maxDelay);
|
|
||||||
if (isRegionRestartScheduled)
|
|
||||||
{
|
|
||||||
// Force a minimum 15 minutes (900 seconds) delay if a region restart is scheduled
|
|
||||||
delaySecs = Math.Max(delaySecs, 900);
|
|
||||||
}
|
|
||||||
|
|
||||||
consecutiveLoginFailures++;
|
|
||||||
LogFormater.Warn($"{reason} - retrying in {delaySecs}s (attempt {consecutiveLoginFailures})...");
|
|
||||||
StartRecoveryTimer(delaySecs * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void BotAlertMessage(object o, AlertMessageEventArgs e)
|
|
||||||
{
|
|
||||||
LogFormater.Info("Client service ~ Alert message: " + e.Message);
|
|
||||||
if (e.Message != null && e.Message.IndexOf("restart", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
||||||
{
|
|
||||||
LogFormater.Warn("Detected upcoming region restart. Initiating graceful logout to prevent limbo state.");
|
|
||||||
isRegionRestartScheduled = true;
|
|
||||||
flagLogoutExpected();
|
|
||||||
if (client != null && client.Network != null)
|
|
||||||
{
|
|
||||||
client.Network.Logout();
|
|
||||||
}
|
|
||||||
// Directly start recovery timer with 15 minutes delay
|
|
||||||
StartRecoveryTimer(900 * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void BotLoggedOut(object o, LoggedOutEventArgs e)
|
protected void BotLoggedOut(object o, LoggedOutEventArgs e)
|
||||||
{
|
{
|
||||||
LogFormater.Info("Client service ~ Logged out");
|
LogFormater.Info("Client service ~ Logged out");
|
||||||
if (!getLogoutExpected())
|
master.TriggerBotClientEvent(true,!getLogoutExpected());
|
||||||
{
|
|
||||||
HandleUnexpectedDisconnect("Unexpected logout");
|
|
||||||
}
|
|
||||||
master.TriggerBotClientEvent(true, !getLogoutExpected());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void BotSimConnected(object o, SimConnectedEventArgs e)
|
protected void BotSimConnected(object o, SimConnectedEventArgs e)
|
||||||
|
|
@ -158,59 +103,28 @@ private BotRejoinConfig rejoinCfg;
|
||||||
AutoRestartLoginTimer.Stop();
|
AutoRestartLoginTimer.Stop();
|
||||||
AutoRestartLoginTimer = null;
|
AutoRestartLoginTimer = null;
|
||||||
}
|
}
|
||||||
isRegionRestartScheduled = false;
|
|
||||||
lastConnectTime = DateTime.UtcNow;
|
|
||||||
goodticks = 0;
|
goodticks = 0;
|
||||||
LogFormater.Info("Client service ~ Connected to sim: " + e.Simulator.Name);
|
LogFormater.Info("Client service ~ Connected to sim: "+e.Simulator.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void BotSimDisconnected(object o, SimDisconnectedEventArgs e)
|
protected void BotSimDisconnected(object o, SimDisconnectedEventArgs e)
|
||||||
{
|
{
|
||||||
goodticks = 0;
|
goodticks = 0;
|
||||||
LogFormater.Info("Client service ~ Disconnected from sim: " + e.Simulator.Name);
|
LogFormater.Info("Client service ~ Disconnected from sim: " + e.Simulator.Name);
|
||||||
if (!getLogoutExpected())
|
|
||||||
{
|
|
||||||
HandleUnexpectedDisconnect("Unexpected sim disconnect");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void BotDisconnected(object o, DisconnectedEventArgs e)
|
protected void BotDisconnected(object o, DisconnectedEventArgs e)
|
||||||
{
|
{
|
||||||
LogFormater.Info("Client service ~ Network disconnected: " + e.Message);
|
LogFormater.Info("Client service ~ Network disconnected: " + e.Message);
|
||||||
if (!getLogoutExpected())
|
|
||||||
{
|
|
||||||
HandleUnexpectedDisconnect("Unexpected network disconnect: " + e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void StartRecoveryTimer(int intervalMs = 5000)
|
|
||||||
{
|
|
||||||
if (AutoRestartLoginTimer == null)
|
|
||||||
{
|
|
||||||
AutoRestartLoginTimer = new Timer
|
|
||||||
{
|
|
||||||
Interval = intervalMs,
|
|
||||||
AutoReset = false
|
|
||||||
};
|
|
||||||
AutoRestartLoginTimer.Elapsed += RestartTimer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AutoRestartLoginTimer.Interval = intervalMs;
|
|
||||||
}
|
|
||||||
AutoRestartLoginTimer.Stop();
|
|
||||||
AutoRestartLoginTimer.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void BotLoginStatus(object o, LoginProgressEventArgs e)
|
protected void BotLoginStatus(object o, LoginProgressEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Status == LoginStatus.Failed || !string.IsNullOrEmpty(e.FailReason))
|
if(e.FailReason != "")
|
||||||
{
|
{
|
||||||
string reason = string.IsNullOrEmpty(e.FailReason) ? e.Message : e.FailReason;
|
LogFormater.Info("Client service ~ {FAILED} Login status: " + e.FailReason.ToString());
|
||||||
LogFormater.Info("Client service ~ {FAILED} Login status: " + reason);
|
|
||||||
client.Network.Logout();
|
client.Network.Logout();
|
||||||
ResetClient();
|
ResetClient();
|
||||||
HandleUnexpectedDisconnect("Login failed: " + reason);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LogFormater.Info("Client service ~ Login status: " + e.Status.ToString());
|
LogFormater.Info("Client service ~ Login status: " + e.Status.ToString());
|
||||||
|
|
@ -295,18 +209,12 @@ private BotRejoinConfig rejoinCfg;
|
||||||
client.Network.Disconnected += BotDisconnected;
|
client.Network.Disconnected += BotDisconnected;
|
||||||
client.Network.SimDisconnected += BotSimDisconnected;
|
client.Network.SimDisconnected += BotSimDisconnected;
|
||||||
client.Network.LoginProgress += BotLoginStatus;
|
client.Network.LoginProgress += BotLoginStatus;
|
||||||
client.Self.AlertMessage += BotAlertMessage;
|
|
||||||
client.Self.AgentDataReply += (s, e) =>
|
|
||||||
{
|
|
||||||
master.DataStoreService.SetActiveGroupTitle(e.GroupTitle);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Login()
|
public void Login()
|
||||||
{
|
{
|
||||||
ResetClient();
|
ResetClient();
|
||||||
goodticks = 0;
|
goodticks = 0;
|
||||||
LogoutExpected = false;
|
|
||||||
master.TriggerBotClientEvent(false, false);
|
master.TriggerBotClientEvent(false, false);
|
||||||
LoginParams loginParams = new(
|
LoginParams loginParams = new(
|
||||||
client,
|
client,
|
||||||
|
|
@ -320,25 +228,6 @@ private BotRejoinConfig rejoinCfg;
|
||||||
{
|
{
|
||||||
loginParams.URI = basicCfg.GetLoginURI();
|
loginParams.URI = basicCfg.GetLoginURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
string customMac = basicCfg.GetMAC();
|
|
||||||
if (!string.IsNullOrEmpty(customMac))
|
|
||||||
{
|
|
||||||
loginParams.MAC = customMac;
|
|
||||||
}
|
|
||||||
|
|
||||||
string customId0 = basicCfg.GetId0();
|
|
||||||
if (!string.IsNullOrEmpty(customId0))
|
|
||||||
{
|
|
||||||
loginParams.ID0 = customId0;
|
|
||||||
}
|
|
||||||
|
|
||||||
string customStart = basicCfg.GetStartLocation();
|
|
||||||
if (!string.IsNullOrEmpty(customStart))
|
|
||||||
{
|
|
||||||
loginParams.Start = customStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoRestartLoginTimer.Start();
|
AutoRestartLoginTimer.Start();
|
||||||
client.Network.BeginLogin(loginParams);
|
client.Network.BeginLogin(loginParams);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using OpenMetaverse.ImportExport.Collada14;
|
using OpenMetaverse.ImportExport.Collada14;
|
||||||
using SecondBotEvents.Config;
|
using SecondBotEvents.Config;
|
||||||
|
|
|
||||||
|
|
@ -522,15 +522,20 @@ namespace SecondBotEvents.Services
|
||||||
AddLink(item);
|
AddLink(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queued swap: Detach everything, wait for SL to settle, then attach new.
|
// Re-implementing ReplaceOutfit logic manually to ensure a clean swap without toggling.
|
||||||
|
var wearables = outfit.OfType<InventoryWearable>().ToList();
|
||||||
|
var attachments = outfit.Where(item => item is InventoryAttachment || item is InventoryObject).ToList();
|
||||||
|
|
||||||
|
// 1. Replace wearables (this is usually safe and doesn't toggle)
|
||||||
|
GetClient().Appearance.ReplaceOutfit(wearables.Cast<InventoryItem>().ToList());
|
||||||
|
|
||||||
|
// 2. Replace attachments with FirstDetachAll=true and replace=true to prevent toggling
|
||||||
|
GetClient().Appearance.AddAttachments(attachments, true, true);
|
||||||
|
|
||||||
ThreadPool.QueueUserWorkItem(sync =>
|
ThreadPool.QueueUserWorkItem(sync =>
|
||||||
{
|
{
|
||||||
var current = ContentLinks().Select(RealInventoryItem).ToList();
|
Thread.Sleep(2000);
|
||||||
RemoveFromOutfit(current);
|
GetClient().Appearance.RequestSetAppearance(true);
|
||||||
|
|
||||||
Thread.Sleep(2000); // Wait for SL servers to process detaches
|
|
||||||
|
|
||||||
AddToOutfit(outfit, false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ namespace SecondBotEvents.Services
|
||||||
public DataStoreService(EventsSecondBot setMaster) : base(setMaster)
|
public DataStoreService(EventsSecondBot setMaster) : base(setMaster)
|
||||||
{
|
{
|
||||||
myConfig = new DataStoreConfig(master.fromEnv,master.fromFolder);
|
myConfig = new DataStoreConfig(master.fromEnv,master.fromFolder);
|
||||||
LoadPendingGroupInvites();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Dictionary<string, UUID> avatarsName2Key = [];
|
protected Dictionary<string, UUID> avatarsName2Key = [];
|
||||||
|
|
@ -457,11 +456,12 @@ namespace SecondBotEvents.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<UUID, string> GetGroupRoles(UUID group, bool force = false)
|
public Dictionary<UUID, string> GetGroupRoles(UUID group)
|
||||||
{
|
{
|
||||||
Dictionary<UUID, string> reply = [];
|
Dictionary<UUID, string> reply = [];
|
||||||
if (force || groupRoles.ContainsKey(group) == false)
|
if (groupRoles.ContainsKey(group) == false)
|
||||||
{
|
{
|
||||||
|
|
||||||
AutoResetEvent fetchEvent = new(false);
|
AutoResetEvent fetchEvent = new(false);
|
||||||
void callback(object sender, GroupRolesDataReplyEventArgs e)
|
void callback(object sender, GroupRolesDataReplyEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
@ -541,14 +541,13 @@ namespace SecondBotEvents.Services
|
||||||
|
|
||||||
public void BotRecordReplyIM(UUID sessionid,string message)
|
public void BotRecordReplyIM(UUID sessionid,string message)
|
||||||
{
|
{
|
||||||
RecordChat(sessionid, "{BOT} " + GetClient().Self.Name, message);
|
RecordChat(sessionid, "Bot", message);
|
||||||
}
|
}
|
||||||
public void BotRecordLocalchatReply(string message, string typeTag = "")
|
public void BotRecordLocalchatReply(string message, string typeTag = "")
|
||||||
{
|
{
|
||||||
lock (localChatHistory)
|
lock (localChatHistory)
|
||||||
{
|
{
|
||||||
long ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
localChatHistory.Add("{BOT} " + typeTag + GetClient().Self.Name + ": " + message);
|
||||||
localChatHistory.Add("[" + ms + "] {BOT} " + typeTag + GetClient().Self.Name + ": " + message);
|
|
||||||
if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit())
|
if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -565,8 +564,7 @@ namespace SecondBotEvents.Services
|
||||||
{
|
{
|
||||||
return; // chat window not open unable to record message
|
return; // chat window not open unable to record message
|
||||||
}
|
}
|
||||||
long ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
chatWindows[sessionID].Add(fromname + ": " + message);
|
||||||
chatWindows[sessionID].Add("[" + ms + "] " + fromname + ": " + message);
|
|
||||||
chatWindowsUnread[sessionID] = true;
|
chatWindowsUnread[sessionID] = true;
|
||||||
int purgeLen = myConfig.GetImChatHistoryLimit();
|
int purgeLen = myConfig.GetImChatHistoryLimit();
|
||||||
if (chatWindowsIsGroup[sessionID] == true)
|
if (chatWindowsIsGroup[sessionID] == true)
|
||||||
|
|
@ -675,49 +673,6 @@ namespace SecondBotEvents.Services
|
||||||
return KnownAvatar(name, UUID.Zero);
|
return KnownAvatar(name, UUID.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string groupInvitesFilePath = "data/pending_group_invites.json";
|
|
||||||
|
|
||||||
private void SavePendingGroupInvites()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string dir = System.IO.Path.GetDirectoryName(groupInvitesFilePath);
|
|
||||||
if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir))
|
|
||||||
{
|
|
||||||
System.IO.Directory.CreateDirectory(dir);
|
|
||||||
}
|
|
||||||
string json = JsonSerializer.Serialize(PendingGroupInvites, JsonOptions.UnsafeRelaxed);
|
|
||||||
System.IO.File.WriteAllText(groupInvitesFilePath, json);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogFormater.Crit("Failed to save pending group invites: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadPendingGroupInvites()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (System.IO.File.Exists(groupInvitesFilePath))
|
|
||||||
{
|
|
||||||
string json = System.IO.File.ReadAllText(groupInvitesFilePath);
|
|
||||||
var loaded = JsonSerializer.Deserialize<List<GroupInvite>>(json);
|
|
||||||
if (loaded != null)
|
|
||||||
{
|
|
||||||
lock (PendingGroupInvites)
|
|
||||||
{
|
|
||||||
PendingGroupInvites = loaded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogFormater.Crit("Failed to load pending group invites: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddPendingGroupInvite(UUID groupID, UUID agentID, string agentName, string message)
|
public void AddPendingGroupInvite(UUID groupID, UUID agentID, string agentName, string message)
|
||||||
{
|
{
|
||||||
lock (PendingGroupInvites)
|
lock (PendingGroupInvites)
|
||||||
|
|
@ -727,50 +682,14 @@ namespace SecondBotEvents.Services
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string groupName = "Unknown Group";
|
|
||||||
if (GetClient().Groups.GroupName2KeyCache.TryGetValue(groupID, out string resolvedCacheName))
|
|
||||||
{
|
|
||||||
groupName = resolvedCacheName;
|
|
||||||
}
|
|
||||||
else if (groupsKey2Name.TryGetValue(groupID, out string cachedName))
|
|
||||||
{
|
|
||||||
groupName = cachedName;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AutoResetEvent fetchEvent = new(false);
|
|
||||||
void callback(object sender, GroupNamesEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.GroupNames.TryGetValue(groupID, out string resolvedName))
|
|
||||||
{
|
|
||||||
groupName = resolvedName;
|
|
||||||
fetchEvent.Set();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GetClient().Groups.GroupNamesReply += callback;
|
|
||||||
GetClient().Groups.RequestGroupName(groupID);
|
|
||||||
fetchEvent.WaitOne(1500, false);
|
|
||||||
GetClient().Groups.GroupNamesReply -= callback;
|
|
||||||
|
|
||||||
if (groupName != "Unknown Group")
|
|
||||||
{
|
|
||||||
lock (groupsKey2Name)
|
|
||||||
{
|
|
||||||
groupsKey2Name[groupID] = groupName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingGroupInvites.Add(new GroupInvite
|
PendingGroupInvites.Add(new GroupInvite
|
||||||
{
|
{
|
||||||
GroupID = groupID,
|
GroupID = groupID,
|
||||||
GroupName = groupName,
|
GroupName = "Unknown Group", // Will be updated on lookup
|
||||||
AgentID = agentID,
|
AgentID = agentID,
|
||||||
AgentName = agentName,
|
AgentName = agentName,
|
||||||
Message = message
|
Message = message
|
||||||
});
|
});
|
||||||
SavePendingGroupInvites();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -787,7 +706,6 @@ namespace SecondBotEvents.Services
|
||||||
lock (PendingGroupInvites)
|
lock (PendingGroupInvites)
|
||||||
{
|
{
|
||||||
PendingGroupInvites.RemoveAll(i => i.GroupID == groupID);
|
PendingGroupInvites.RemoveAll(i => i.GroupID == groupID);
|
||||||
SavePendingGroupInvites();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1236,35 +1154,12 @@ namespace SecondBotEvents.Services
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string activeGroupTitle = "";
|
Dictionary<UUID, Avatar.AvatarProperties> avprops = [];
|
||||||
protected Dictionary<UUID, Avatar.AvatarProperties> avprops = [];
|
Dictionary<UUID, Dictionary<UUID, string>> avpicks = [];
|
||||||
protected Dictionary<UUID, Dictionary<UUID, string>> avpicks = [];
|
|
||||||
protected Dictionary<UUID, Dictionary<UUID, string>> avclassifieds = [];
|
|
||||||
protected Dictionary<UUID, Dictionary<UUID, ProfilePick>> avpicksDetails = [];
|
|
||||||
protected Dictionary<UUID, Dictionary<UUID, ClassifiedAd>> avclassifiedsDetails = [];
|
|
||||||
protected Dictionary<UUID, List<AvatarGroup>> avgroups = [];
|
|
||||||
|
|
||||||
public void ClearAvatarPicksCache(UUID agentID)
|
|
||||||
{
|
|
||||||
avpicks.Remove(agentID);
|
|
||||||
lock (avpicksDetails)
|
|
||||||
{
|
|
||||||
avpicksDetails.Remove(agentID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearAvatarClassifiedsCache(UUID agentID)
|
|
||||||
{
|
|
||||||
avclassifieds.Remove(agentID);
|
|
||||||
lock (avclassifiedsDetails)
|
|
||||||
{
|
|
||||||
avclassifiedsDetails.Remove(agentID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyValuePair<bool, Dictionary<UUID, string>> GetAvatarPicks(UUID agentID)
|
public KeyValuePair<bool, Dictionary<UUID, string>> GetAvatarPicks(UUID agentID)
|
||||||
{
|
{
|
||||||
if (!avpicks.ContainsKey(agentID))
|
if (avpicks.ContainsKey(agentID) == false)
|
||||||
{
|
{
|
||||||
GetClient().Avatars.RequestAvatarPicks(agentID);
|
GetClient().Avatars.RequestAvatarPicks(agentID);
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, string>>(false, []);
|
return new KeyValuePair<bool, Dictionary<UUID, string>>(false, []);
|
||||||
|
|
@ -1272,259 +1167,21 @@ namespace SecondBotEvents.Services
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, string>>(true, avpicks[agentID]);
|
return new KeyValuePair<bool, Dictionary<UUID, string>>(true, avpicks[agentID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValuePair<bool, Dictionary<UUID, ProfilePick>> GetAvatarPicksDetailed(UUID agentID)
|
|
||||||
{
|
|
||||||
if (!avpicks.ContainsKey(agentID))
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestAvatarPicks(agentID);
|
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, ProfilePick>>(false, []);
|
|
||||||
}
|
|
||||||
lock (avpicksDetails)
|
|
||||||
{
|
|
||||||
if (!avpicksDetails.ContainsKey(agentID))
|
|
||||||
{
|
|
||||||
avpicksDetails.Add(agentID, []);
|
|
||||||
foreach (UUID key in avpicks[agentID].Keys)
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestPickInfo(agentID, key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (UUID pickID in new List<UUID>(avpicksDetails[agentID].Keys))
|
|
||||||
{
|
|
||||||
ProfilePick value = avpicksDetails[agentID][pickID];
|
|
||||||
if (!string.IsNullOrEmpty(value.SimName) || !(value.PosGlobal.X > 0.0) || !(value.PosGlobal.Y > 0.0))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
float localX;
|
|
||||||
float localY;
|
|
||||||
ulong handle = Helpers.GlobalPosToRegionHandle((float)value.PosGlobal.X, (float)value.PosGlobal.Y, out localX, out localY);
|
|
||||||
if (GetClient().Grid.RegionsByHandle.TryGetValue(handle, out var value2))
|
|
||||||
{
|
|
||||||
value.SimName = value2.Name;
|
|
||||||
avpicksDetails[agentID][pickID] = value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Task.Run(delegate
|
|
||||||
{
|
|
||||||
if (GetClient().Grid.GetGridRegion(handle, GridLayerType.Terrain, out var region))
|
|
||||||
{
|
|
||||||
lock (avpicksDetails)
|
|
||||||
{
|
|
||||||
if (avpicksDetails.ContainsKey(agentID) && avpicksDetails[agentID].ContainsKey(pickID))
|
|
||||||
{
|
|
||||||
ProfilePick value3 = avpicksDetails[agentID][pickID];
|
|
||||||
value3.SimName = region.Name;
|
|
||||||
avpicksDetails[agentID][pickID] = value3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, ProfilePick>>(true, avpicksDetails[agentID]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AvatarPicksReply(object sender, AvatarPicksReplyEventArgs e)
|
protected void AvatarPicksReply(object sender, AvatarPicksReplyEventArgs e)
|
||||||
{
|
{
|
||||||
lock (avpicks)
|
lock (avpicks)
|
||||||
{
|
{
|
||||||
if (!avpicks.ContainsKey(e.AvatarID))
|
if (avpicks.ContainsKey(e.AvatarID) == false)
|
||||||
{
|
{
|
||||||
avpicks.Add(e.AvatarID, []);
|
avpicks.Add(e.AvatarID, []);
|
||||||
}
|
}
|
||||||
avpicks[e.AvatarID] = e.Picks;
|
avpicks[e.AvatarID] = e.Picks;
|
||||||
lock (avpicksDetails)
|
|
||||||
{
|
|
||||||
if (!avpicksDetails.ContainsKey(e.AvatarID))
|
|
||||||
{
|
|
||||||
avpicksDetails.Add(e.AvatarID, []);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
List<UUID> list = [];
|
|
||||||
foreach (UUID key in avpicksDetails[e.AvatarID].Keys)
|
|
||||||
{
|
|
||||||
if (!e.Picks.ContainsKey(key))
|
|
||||||
{
|
|
||||||
list.Add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (UUID item in list)
|
|
||||||
{
|
|
||||||
avpicksDetails[e.AvatarID].Remove(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (UUID key2 in e.Picks.Keys)
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestPickInfo(e.AvatarID, key2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void PickInfoReply(object sender, PickInfoReplyEventArgs e)
|
public KeyValuePair<bool,Avatar.AvatarProperties> GetAvatarAvatarProperties(UUID agentID)
|
||||||
{
|
{
|
||||||
lock (avpicksDetails)
|
if (avprops.ContainsKey(agentID) == false)
|
||||||
{
|
|
||||||
UUID avatarID = e.Pick.CreatorID;
|
|
||||||
if (avatarID == UUID.Zero)
|
|
||||||
{
|
|
||||||
lock (avpicks)
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<UUID, Dictionary<UUID, string>> avpick in avpicks)
|
|
||||||
{
|
|
||||||
if (avpick.Value.ContainsKey(e.PickID))
|
|
||||||
{
|
|
||||||
avatarID = avpick.Key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (avatarID == UUID.Zero)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!avpicksDetails.ContainsKey(avatarID))
|
|
||||||
{
|
|
||||||
avpicksDetails.Add(avatarID, []);
|
|
||||||
}
|
|
||||||
ProfilePick pick = e.Pick;
|
|
||||||
if (string.IsNullOrEmpty(pick.SimName) && pick.PosGlobal.X > 0.0 && pick.PosGlobal.Y > 0.0)
|
|
||||||
{
|
|
||||||
float localX;
|
|
||||||
float localY;
|
|
||||||
ulong handle = Helpers.GlobalPosToRegionHandle((float)pick.PosGlobal.X, (float)pick.PosGlobal.Y, out localX, out localY);
|
|
||||||
if (GetClient().Grid.RegionsByHandle.TryGetValue(handle, out var value))
|
|
||||||
{
|
|
||||||
pick.SimName = value.Name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Task.Run(delegate
|
|
||||||
{
|
|
||||||
if (GetClient().Grid.GetGridRegion(handle, GridLayerType.Terrain, out var region))
|
|
||||||
{
|
|
||||||
lock (avpicksDetails)
|
|
||||||
{
|
|
||||||
if (avpicksDetails.ContainsKey(avatarID) && avpicksDetails[avatarID].ContainsKey(e.PickID))
|
|
||||||
{
|
|
||||||
ProfilePick value2 = avpicksDetails[avatarID][e.PickID];
|
|
||||||
value2.SimName = region.Name;
|
|
||||||
avpicksDetails[avatarID][e.PickID] = value2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
avpicksDetails[avatarID][e.PickID] = pick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyValuePair<bool, Dictionary<UUID, string>> GetAvatarClassifieds(UUID agentID)
|
|
||||||
{
|
|
||||||
if (!avclassifieds.ContainsKey(agentID))
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestAvatarClassified(agentID);
|
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, string>>(false, []);
|
|
||||||
}
|
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, string>>(true, avclassifieds[agentID]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyValuePair<bool, Dictionary<UUID, ClassifiedAd>> GetAvatarClassifiedsDetailed(UUID agentID)
|
|
||||||
{
|
|
||||||
if (!avclassifieds.ContainsKey(agentID))
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestAvatarClassified(agentID);
|
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, ClassifiedAd>>(false, []);
|
|
||||||
}
|
|
||||||
lock (avclassifiedsDetails)
|
|
||||||
{
|
|
||||||
if (!avclassifiedsDetails.ContainsKey(agentID))
|
|
||||||
{
|
|
||||||
avclassifiedsDetails.Add(agentID, []);
|
|
||||||
foreach (UUID key in avclassifieds[agentID].Keys)
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestClassifiedInfo(agentID, key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new KeyValuePair<bool, Dictionary<UUID, ClassifiedAd>>(true, avclassifiedsDetails[agentID]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AvatarClassifiedsReply(object sender, AvatarClassifiedReplyEventArgs e)
|
|
||||||
{
|
|
||||||
lock (avclassifieds)
|
|
||||||
{
|
|
||||||
if (!avclassifieds.ContainsKey(e.AvatarID))
|
|
||||||
{
|
|
||||||
avclassifieds.Add(e.AvatarID, []);
|
|
||||||
}
|
|
||||||
avclassifieds[e.AvatarID] = e.Classifieds;
|
|
||||||
lock (avclassifiedsDetails)
|
|
||||||
{
|
|
||||||
if (!avclassifiedsDetails.ContainsKey(e.AvatarID))
|
|
||||||
{
|
|
||||||
avclassifiedsDetails.Add(e.AvatarID, []);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
List<UUID> list = [];
|
|
||||||
foreach (UUID key in avclassifiedsDetails[e.AvatarID].Keys)
|
|
||||||
{
|
|
||||||
if (!e.Classifieds.ContainsKey(key))
|
|
||||||
{
|
|
||||||
list.Add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (UUID item in list)
|
|
||||||
{
|
|
||||||
avclassifiedsDetails[e.AvatarID].Remove(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (UUID key2 in e.Classifieds.Keys)
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestClassifiedInfo(e.AvatarID, key2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void ClassifiedInfoReply(object sender, ClassifiedInfoReplyEventArgs e)
|
|
||||||
{
|
|
||||||
lock (avclassifiedsDetails)
|
|
||||||
{
|
|
||||||
UUID uUID = UUID.Zero;
|
|
||||||
lock (avclassifieds)
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<UUID, Dictionary<UUID, string>> avclassified in avclassifieds)
|
|
||||||
{
|
|
||||||
if (avclassified.Value.ContainsKey(e.ClassifiedID))
|
|
||||||
{
|
|
||||||
uUID = avclassified.Key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (uUID != UUID.Zero)
|
|
||||||
{
|
|
||||||
if (!avclassifiedsDetails.ContainsKey(uUID))
|
|
||||||
{
|
|
||||||
avclassifiedsDetails.Add(uUID, []);
|
|
||||||
}
|
|
||||||
avclassifiedsDetails[uUID][e.ClassifiedID] = e.Classified;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyValuePair<bool, Avatar.AvatarProperties> GetAvatarAvatarProperties(UUID agentID)
|
|
||||||
{
|
|
||||||
if (!avprops.ContainsKey(agentID))
|
|
||||||
{
|
{
|
||||||
GetClient().Avatars.RequestAvatarProperties(agentID);
|
GetClient().Avatars.RequestAvatarProperties(agentID);
|
||||||
return new KeyValuePair<bool, Avatar.AvatarProperties>(false, new Avatar.AvatarProperties());
|
return new KeyValuePair<bool, Avatar.AvatarProperties>(false, new Avatar.AvatarProperties());
|
||||||
|
|
@ -1532,51 +1189,15 @@ namespace SecondBotEvents.Services
|
||||||
return new KeyValuePair<bool, Avatar.AvatarProperties>(true, avprops[agentID]);
|
return new KeyValuePair<bool, Avatar.AvatarProperties>(true, avprops[agentID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAvatarProperties(UUID agentID, Avatar.AvatarProperties properties)
|
|
||||||
{
|
|
||||||
lock (avprops)
|
|
||||||
{
|
|
||||||
avprops[agentID] = properties;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AvatarDetailsReply(object sender, AvatarPropertiesReplyEventArgs e)
|
protected void AvatarDetailsReply(object sender, AvatarPropertiesReplyEventArgs e)
|
||||||
{
|
{
|
||||||
if (!avprops.ContainsKey(e.AvatarID))
|
if(avprops.ContainsKey(e.AvatarID) == false)
|
||||||
{
|
{
|
||||||
avprops.Add(e.AvatarID, new Avatar.AvatarProperties());
|
avprops.Add(e.AvatarID,new Avatar.AvatarProperties());
|
||||||
}
|
}
|
||||||
avprops[e.AvatarID] = e.Properties;
|
avprops[e.AvatarID] = e.Properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValuePair<bool, List<AvatarGroup>> GetAvatarGroups(UUID agentID)
|
|
||||||
{
|
|
||||||
if (!avgroups.ContainsKey(agentID))
|
|
||||||
{
|
|
||||||
GetClient().Avatars.RequestAvatarProperties(agentID);
|
|
||||||
return new KeyValuePair<bool, List<AvatarGroup>>(false, []);
|
|
||||||
}
|
|
||||||
return new KeyValuePair<bool, List<AvatarGroup>>(true, avgroups[agentID]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AvatarGroupsReplyCallback(object sender, AvatarGroupsReplyEventArgs e)
|
|
||||||
{
|
|
||||||
lock (avgroups)
|
|
||||||
{
|
|
||||||
avgroups[e.AvatarID] = e.Groups;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetActiveGroupTitle()
|
|
||||||
{
|
|
||||||
return activeGroupTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetActiveGroupTitle(string title)
|
|
||||||
{
|
|
||||||
activeGroupTitle = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AttachEventsAfterDelay()
|
protected void AttachEventsAfterDelay()
|
||||||
{
|
{
|
||||||
GetClient().Groups.CurrentGroups += GroupCurrent;
|
GetClient().Groups.CurrentGroups += GroupCurrent;
|
||||||
|
|
@ -1592,11 +1213,7 @@ namespace SecondBotEvents.Services
|
||||||
GetClient().Estate.EstateBansReply += EstateBans;
|
GetClient().Estate.EstateBansReply += EstateBans;
|
||||||
GetClient().Friends.FriendFoundReply += FindFriendReply;
|
GetClient().Friends.FriendFoundReply += FindFriendReply;
|
||||||
GetClient().Avatars.AvatarPropertiesReply += AvatarDetailsReply;
|
GetClient().Avatars.AvatarPropertiesReply += AvatarDetailsReply;
|
||||||
GetClient().Avatars.AvatarGroupsReply += AvatarGroupsReplyCallback;
|
|
||||||
GetClient().Avatars.AvatarPicksReply += AvatarPicksReply;
|
GetClient().Avatars.AvatarPicksReply += AvatarPicksReply;
|
||||||
GetClient().Avatars.AvatarClassifiedReply += AvatarClassifiedsReply;
|
|
||||||
GetClient().Avatars.PickInfoReply += PickInfoReply;
|
|
||||||
GetClient().Avatars.ClassifiedInfoReply += ClassifiedInfoReply;
|
|
||||||
GetClient().Groups.RequestCurrentGroups();
|
GetClient().Groups.RequestCurrentGroups();
|
||||||
_ = GetClient().Self.RetrieveInstantMessages();
|
_ = GetClient().Self.RetrieveInstantMessages();
|
||||||
if(myConfig.GetPrefetchInventory() == true)
|
if(myConfig.GetPrefetchInventory() == true)
|
||||||
|
|
@ -1734,8 +1351,7 @@ namespace SecondBotEvents.Services
|
||||||
AddAvatar(e.SourceID, e.FromName);
|
AddAvatar(e.SourceID, e.FromName);
|
||||||
}
|
}
|
||||||
|
|
||||||
long ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
localChatHistory.Add(source + " " + typeTag + e.FromName + ": " + e.Message);
|
||||||
localChatHistory.Add("[" + ms + "] " + source + " " + typeTag + e.FromName + ": " + e.Message);
|
|
||||||
if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit())
|
if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit())
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
|
@ -1893,14 +1509,7 @@ namespace SecondBotEvents.Services
|
||||||
{
|
{
|
||||||
// group IM
|
// group IM
|
||||||
OpenChatWindow(true, e.IM.IMSessionID, e.IM.IMSessionID);
|
OpenChatWindow(true, e.IM.IMSessionID, e.IM.IMSessionID);
|
||||||
if (e.IM.FromAgentID == GetClient().Self.AgentID)
|
|
||||||
{
|
|
||||||
RecordChat(e.IM.IMSessionID, "{BOT} " + GetClient().Self.Name, e.IM.Message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RecordChat(e.IM.IMSessionID, e.IM.FromAgentName, e.IM.Message);
|
RecordChat(e.IM.IMSessionID, e.IM.FromAgentName, e.IM.Message);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
UUID SessionID = e.IM.IMSessionID;
|
UUID SessionID = e.IM.IMSessionID;
|
||||||
|
|
@ -1915,10 +1524,6 @@ namespace SecondBotEvents.Services
|
||||||
{
|
{
|
||||||
RecordChat(SessionID, e.IM.FromAgentName, e.IM.Message);
|
RecordChat(SessionID, e.IM.FromAgentName, e.IM.Message);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
RecordChat(SessionID, "{BOT} " + GetClient().Self.Name, e.IM.Message);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case InstantMessageDialog.FriendshipOffered:
|
case InstantMessageDialog.FriendshipOffered:
|
||||||
|
|
@ -1972,11 +1577,6 @@ namespace SecondBotEvents.Services
|
||||||
GetClient().Estate.EstateBansReply -= EstateBans;
|
GetClient().Estate.EstateBansReply -= EstateBans;
|
||||||
GetClient().Friends.FriendFoundReply -= FindFriendReply;
|
GetClient().Friends.FriendFoundReply -= FindFriendReply;
|
||||||
GetClient().Avatars.AvatarPropertiesReply -= AvatarDetailsReply;
|
GetClient().Avatars.AvatarPropertiesReply -= AvatarDetailsReply;
|
||||||
GetClient().Avatars.AvatarGroupsReply -= AvatarGroupsReplyCallback;
|
|
||||||
GetClient().Avatars.AvatarPicksReply -= AvatarPicksReply;
|
|
||||||
GetClient().Avatars.AvatarClassifiedReply -= AvatarClassifiedsReply;
|
|
||||||
GetClient().Avatars.PickInfoReply -= PickInfoReply;
|
|
||||||
GetClient().Avatars.ClassifiedInfoReply -= ClassifiedInfoReply;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using SecondBotEvents.Config;
|
using SecondBotEvents.Config;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -108,28 +108,6 @@ namespace SecondBotEvents.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<DialogWindow> GetActiveDialogs()
|
|
||||||
{
|
|
||||||
lock (DialogWindows)
|
|
||||||
{
|
|
||||||
CleanUp();
|
|
||||||
List<DialogWindow> reply = [];
|
|
||||||
foreach (var kvp in DialogWindows)
|
|
||||||
{
|
|
||||||
var e = kvp.Value;
|
|
||||||
reply.Add(new DialogWindow
|
|
||||||
{
|
|
||||||
buttons = [.. e.ButtonLabels],
|
|
||||||
dialogid = kvp.Key,
|
|
||||||
message = e.Message,
|
|
||||||
objectname = e.ObjectName,
|
|
||||||
expires = DialogWindowsExpire[kvp.Key]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DialogAction(int dialogID, string button)
|
public string DialogAction(int dialogID, string button)
|
||||||
{
|
{
|
||||||
lock(DialogWindows)
|
lock(DialogWindows)
|
||||||
|
|
@ -163,7 +141,7 @@ namespace SecondBotEvents.Services
|
||||||
List<int> removeids = [];
|
List<int> removeids = [];
|
||||||
foreach(KeyValuePair<int,long> a in DialogWindowsExpire)
|
foreach(KeyValuePair<int,long> a in DialogWindowsExpire)
|
||||||
{
|
{
|
||||||
if(a.Value < unixtime)
|
if(a.Value > unixtime)
|
||||||
{
|
{
|
||||||
removeids.Add(a.Key);
|
removeids.Add(a.Key);
|
||||||
}
|
}
|
||||||
|
|
@ -262,8 +240,6 @@ namespace SecondBotEvents.Services
|
||||||
if (GetClient() != null)
|
if (GetClient() != null)
|
||||||
{
|
{
|
||||||
GetClient().Self.ScriptDialog -= DialogWindowEvent;
|
GetClient().Self.ScriptDialog -= DialogWindowEvent;
|
||||||
GetClient().Self.LoadURL -= LoadUrlEvent;
|
|
||||||
GetClient().Self.AlertMessage -= BotAlertMessage;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,37 +249,6 @@ namespace SecondBotEvents.Services
|
||||||
botConnected = true;
|
botConnected = true;
|
||||||
// attach events
|
// attach events
|
||||||
GetClient().Self.ScriptDialog += DialogWindowEvent;
|
GetClient().Self.ScriptDialog += DialogWindowEvent;
|
||||||
GetClient().Self.LoadURL += LoadUrlEvent;
|
|
||||||
GetClient().Self.AlertMessage += BotAlertMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Attach()
|
|
||||||
{
|
|
||||||
botConnected = true;
|
|
||||||
// attach events
|
|
||||||
GetClient().Self.ScriptDialog += DialogWindowEvent;
|
|
||||||
GetClient().Self.LoadURL += LoadUrlEvent;
|
|
||||||
GetClient().Self.AlertMessage += BotAlertMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void BotAlertMessage(object sender, AlertMessageEventArgs e)
|
|
||||||
{
|
|
||||||
string message = " [SYSTEM] ALERT: " + e.Message;
|
|
||||||
master.DataStoreService.BotRecordLocalchatReply(message, "[SYSTEM] ");
|
|
||||||
LogFormater.Info("[AlertMessage] " + e.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void LoadUrlEvent(object sender, LoadUrlEventArgs e)
|
|
||||||
{
|
|
||||||
// We'll relay this as a special chat message so it shows up in the dashboard chat log
|
|
||||||
// In this version of LibOMV, the property is 'URL' (all caps).
|
|
||||||
string address = e.URL;
|
|
||||||
string message = " [SYSTEM] Object '" + e.ObjectName + "' sent a link: " + e.Message + " URL: " + address;
|
|
||||||
|
|
||||||
// Record it in the bot's local chat history so the dashboard picks it up on the next poll
|
|
||||||
master.DataStoreService.BotRecordLocalchatReply(message, "[SYSTEM] ");
|
|
||||||
|
|
||||||
LogFormater.Info("[LoadURL] " + e.ObjectName + " sent: " + e.Message + " (" + address + ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Start(bool updateEnabled = false, bool setEnabledTo = false)
|
public override void Start(bool updateEnabled = false, bool setEnabledTo = false)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
|
|
@ -1300,7 +1300,7 @@ namespace SecondBotEvents.Services
|
||||||
SocketRole role = server.GetRole(roleid);
|
SocketRole role = server.GetRole(roleid);
|
||||||
Dictionary<string, string> settings = new()
|
Dictionary<string, string> settings = new()
|
||||||
{
|
{
|
||||||
{ "Config|Color", $"{role.Color.R},{role.Color.G},{role.Color.B}" },
|
{ "Config|Color", String.Join(",", [role.Color.R, role.Color.G, role.Color.B]) },
|
||||||
{ "Config|Name", role.Name },
|
{ "Config|Name", role.Name },
|
||||||
{ "Info|AssignedCount", role.Members.ToString() },
|
{ "Info|AssignedCount", role.Members.ToString() },
|
||||||
{ "Perm|AttachFiles", role.Permissions.AttachFiles.ToString() },
|
{ "Perm|AttachFiles", role.Permissions.AttachFiles.ToString() },
|
||||||
|
|
|
||||||
|
|
@ -127,11 +127,9 @@ namespace SecondBotEvents.Services
|
||||||
|
|
||||||
private void CreateWebServer()
|
private void CreateWebServer()
|
||||||
{
|
{
|
||||||
// Swan.Logging.Logger.UnregisterLogger<ConsoleLogger>();
|
Swan.Logging.Logger.UnregisterLogger<ConsoleLogger>();
|
||||||
string hostName = System.Net.Dns.GetHostName();
|
|
||||||
int port = myConfig.GetPort();
|
|
||||||
HTTPendpoint = new WebServer(o => o
|
HTTPendpoint = new WebServer(o => o
|
||||||
.WithUrlPrefix($"http://{hostName}:{port}/")
|
.WithUrlPrefix("http://*:80")
|
||||||
.WithMode(HttpListenerMode.EmbedIO))
|
.WithMode(HttpListenerMode.EmbedIO))
|
||||||
.WithCors()
|
.WithCors()
|
||||||
.WithWebApi("/api", SerializationCallback, m => m.WithController(() => new HttpWebBot(master)))
|
.WithWebApi("/api", SerializationCallback, m => m.WithController(() => new HttpWebBot(master)))
|
||||||
|
|
@ -139,7 +137,7 @@ namespace SecondBotEvents.Services
|
||||||
.WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Error" }))
|
.WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Error" }))
|
||||||
);
|
);
|
||||||
HTTPendpoint.RunAsync();
|
HTTPendpoint.RunAsync();
|
||||||
LogFormater.Info($"HTTP service [Enabled] on {hostName}:{port}");
|
LogFormater.Info("HTTP service [Enabled] on port 80");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string JsonInput(IHttpContext httpContext)
|
protected string JsonInput(IHttpContext httpContext)
|
||||||
|
|
@ -401,9 +399,9 @@ namespace SecondBotEvents.Services
|
||||||
public string Run([FormField] string commandName, [FormField] string args, [FormField] string signing, [FormField] string unixtime)
|
public string Run([FormField] string commandName, [FormField] string args, [FormField] string signing, [FormField] string unixtime)
|
||||||
{
|
{
|
||||||
string[] myArgs = [];
|
string[] myArgs = [];
|
||||||
if(!string.IsNullOrEmpty(args))
|
if(args != null)
|
||||||
{
|
{
|
||||||
myArgs = args.Split("~#~", StringSplitOptions.None);
|
myArgs = args.Split("~#~", StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
}
|
||||||
if(int.TryParse(unixtime, out int cmdUnixtime) == false)
|
if(int.TryParse(unixtime, out int cmdUnixtime) == false)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using SecondBotEvents.Config;
|
using SecondBotEvents.Config;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -209,7 +209,6 @@ namespace SecondBotEvents.Services
|
||||||
connection.CloseAsync().Await();
|
connection.CloseAsync().Await();
|
||||||
connection = null;
|
connection = null;
|
||||||
}
|
}
|
||||||
channels.Clear();
|
|
||||||
var endpoints = new System.Collections.Generic.List<AmqpTcpEndpoint> {
|
var endpoints = new System.Collections.Generic.List<AmqpTcpEndpoint> {
|
||||||
new AmqpTcpEndpoint(myConfig.GetHostIP(),myConfig.GetHostPort()),
|
new AmqpTcpEndpoint(myConfig.GetHostIP(),myConfig.GetHostPort()),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using OpenMetaverse.ImportExport.Collada14;
|
using OpenMetaverse.ImportExport.Collada14;
|
||||||
using SecondBotEvents.Services;
|
using SecondBotEvents.Services;
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -131,24 +131,8 @@ namespace BetterSecondBot.Static
|
||||||
{
|
{
|
||||||
if (contents.Count > 0)
|
if (contents.Count > 0)
|
||||||
{
|
{
|
||||||
HashSet<UUID> seenIds = [];
|
|
||||||
foreach (InventoryBase item in contents)
|
foreach (InventoryBase item in contents)
|
||||||
{
|
{
|
||||||
if (item is InventoryItem itm)
|
|
||||||
{
|
|
||||||
UUID effectiveID = itm.UUID;
|
|
||||||
// If it's a link, the AssetUUID is the UUID of the item it points to
|
|
||||||
if (itm.AssetType == AssetType.Link)
|
|
||||||
{
|
|
||||||
effectiveID = itm.AssetUUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seenIds.Contains(effectiveID))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seenIds.Add(effectiveID);
|
|
||||||
|
|
||||||
mapitem = new InventoryMapItem
|
mapitem = new InventoryMapItem
|
||||||
{
|
{
|
||||||
name = item.Name,
|
name = item.Name,
|
||||||
|
|
@ -157,17 +141,6 @@ namespace BetterSecondBot.Static
|
||||||
};
|
};
|
||||||
entrys.Add(mapitem);
|
entrys.Add(mapitem);
|
||||||
}
|
}
|
||||||
else if (item is InventoryFolder fold)
|
|
||||||
{
|
|
||||||
mapitem = new InventoryMapItem
|
|
||||||
{
|
|
||||||
name = fold.Name,
|
|
||||||
id = fold.UUID.ToString(),
|
|
||||||
typename = "Folder"
|
|
||||||
};
|
|
||||||
entrys.Add(mapitem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entrys;
|
return entrys;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue