feat: enhance bot commands, group handling, friend management, and avatar pick retrieval
This commit is contained in:
parent
ad6fb6a8d1
commit
5627b66229
7 changed files with 308 additions and 21 deletions
|
|
@ -216,6 +216,27 @@ namespace SecondBotEvents.Commands
|
|||
}
|
||||
return BasicReply(reply.Value.AboutText.ToString());
|
||||
}
|
||||
|
||||
[About("Requests the given avatars profile picks")]
|
||||
[ReturnHints("JSON array of picks")]
|
||||
[ReturnHintsFailure("Invaild avatar uuid")]
|
||||
[ReturnHints("Requesting avatar details [Retry later]")]
|
||||
[ArgHints("avatar", "Who are we getting the profile picks of", "AVATAR")]
|
||||
[CmdTypeGet()]
|
||||
public object GetAvatarPicks(string avatar)
|
||||
{
|
||||
ProcessAvatar(avatar);
|
||||
if (avataruuid == UUID.Zero)
|
||||
{
|
||||
return Failure("Invaild avatar uuid", [avatar]);
|
||||
}
|
||||
KeyValuePair<bool, Dictionary<UUID, string>> reply = master.DataStoreService.GetAvatarPicks(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 display name")]
|
||||
[ReturnHints("display name if set or ?")]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,14 @@ namespace SecondBotEvents.Commands
|
|||
{
|
||||
return BasicReply("world");
|
||||
}
|
||||
|
||||
[About("Returns the bot's Second Life name")]
|
||||
[ReturnHints("Bot Name")]
|
||||
[CmdTypeGet()]
|
||||
public object GetBotName()
|
||||
{
|
||||
return BasicReply(GetClient().Self.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandLibCall
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using OpenMetaverse;
|
||||
using OpenMetaverse;
|
||||
using SecondBotEvents.Services;
|
||||
using System.Text.Json;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -129,6 +129,56 @@ namespace SecondBotEvents.Commands
|
|||
return BasicReply("Request sent", [avatar, state]);
|
||||
|
||||
}
|
||||
|
||||
[About("Gets the pending friend requests")]
|
||||
[ReturnHints("array of FriendRequest")]
|
||||
[CmdTypeGet()]
|
||||
public object FriendsPendingRequests()
|
||||
{
|
||||
List<FriendRequest> requests = master.DataStoreService.GetPendingFriendRequests();
|
||||
return BasicReply(JsonSerializer.Serialize(requests, JsonOptions.UnsafeRelaxed));
|
||||
}
|
||||
|
||||
[About("Responds to a friend request")]
|
||||
[ArgHints("avatar", "Who to respond to","AVATAR")]
|
||||
[ArgHints("accept", "true: Accept, false: Decline","BOOL")]
|
||||
[ReturnHints("accepted")]
|
||||
[ReturnHints("declined")]
|
||||
[ReturnHintsFailure("avatar lookup")]
|
||||
[ReturnHintsFailure("no pending request")]
|
||||
[ReturnHintsFailure("state invaild")]
|
||||
[CmdTypeDo()]
|
||||
public object FriendRequestRespond(string avatar, string accept)
|
||||
{
|
||||
ProcessAvatar(avatar);
|
||||
if (avataruuid == UUID.Zero)
|
||||
{
|
||||
return Failure("avatar lookup", [avatar, accept]);
|
||||
}
|
||||
if (bool.TryParse(accept, out bool status) == false)
|
||||
{
|
||||
return Failure("state invaild", [avatar, accept]);
|
||||
}
|
||||
List<FriendRequest> requests = master.DataStoreService.GetPendingFriendRequests();
|
||||
FriendRequest request = requests.FirstOrDefault(r => r.AgentID == avataruuid);
|
||||
if (request == null)
|
||||
{
|
||||
return Failure("no pending request", [avatar, accept]);
|
||||
}
|
||||
|
||||
if (status == true)
|
||||
{
|
||||
GetClient().Friends.AcceptFriendship(avataruuid, request.SessionID);
|
||||
master.DataStoreService.RemovePendingFriendRequest(avataruuid);
|
||||
return BasicReply("accepted", [avatar, accept]);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetClient().Friends.DeclineFriendship(avataruuid, request.SessionID);
|
||||
master.DataStoreService.RemovePendingFriendRequest(avataruuid);
|
||||
return BasicReply("declined", [avatar, accept]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FriendListEntry
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ namespace SecondBotEvents.Commands
|
|||
return BasicReply(master.DataStoreService.IsGroupMember(groupuuid, avataruuid).ToString());
|
||||
}
|
||||
|
||||
[About("Gets the rank of an avatar in a group (Guest, Member, Officer, Owner)")]
|
||||
[ArgHints("group", "the group to check", "UUID")]
|
||||
[ArgHints("avatar", "the avatar to check", "AVATAR")]
|
||||
[CmdTypeGet()]
|
||||
public object GetAvatarGroupRank(string group, string avatar)
|
||||
{
|
||||
if (UUID.TryParse(group, out UUID groupuuid) == false)
|
||||
{
|
||||
return Failure("Invaild group UUID", [group, avatar]);
|
||||
}
|
||||
ProcessAvatar(avatar);
|
||||
return BasicReply(master.DataStoreService.GetGroupMemberRank(groupuuid, avataruuid));
|
||||
}
|
||||
|
||||
[About("Gets membership of a group")]
|
||||
[ReturnHints("list of UUIDS of group members")]
|
||||
[ReturnHintsFailure("Updating")]
|
||||
|
|
|
|||
|
|
@ -4,12 +4,38 @@ using SecondBotEvents.Services;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace SecondBotEvents.Commands
|
||||
{
|
||||
[ClassInfo("What is going on with the bot")]
|
||||
public class Info(EventsSecondBot setmaster) : CommandsAPI(setmaster)
|
||||
{
|
||||
[About("Requests a 6-digit login token to be sent via IM to the avatar")]
|
||||
[ArgHints("avatar", "The avatar to send the token to", "AVATAR")]
|
||||
[CmdTypeDo()]
|
||||
public object RequestLoginToken(string avatar)
|
||||
{
|
||||
ProcessAvatar(avatar);
|
||||
if (avataruuid == UUID.Zero) return Failure("Unknown avatar - are you sure the name is correct? (Firstname Lastname)");
|
||||
|
||||
string token = master.DataStoreService.RequestLoginToken(avataruuid);
|
||||
GetClient().Self.InstantMessage(avataruuid, $"[Bot Dashboard] Your login verification code is: {token} (Expires in 5 minutes)");
|
||||
return BasicReply("Sent");
|
||||
}
|
||||
|
||||
[About("Verifies a 6-digit login token")]
|
||||
[ArgHints("avatar", "The avatar verifying", "AVATAR")]
|
||||
[ArgHints("token", "The 6-digit token", "STRING")]
|
||||
[CmdTypeGet()]
|
||||
public object VerifyLoginToken(string avatar, string token)
|
||||
{
|
||||
ProcessAvatar(avatar);
|
||||
if (avataruuid == UUID.Zero) return Failure("Unknown avatar");
|
||||
bool ok = master.DataStoreService.VerifyLoginToken(avataruuid, token);
|
||||
return BasicReply(ok.ToString());
|
||||
}
|
||||
|
||||
[About("Fetchs the current region type the bot is in")]
|
||||
[ReturnHints("Region type as string")]
|
||||
[ReturnHintsFailure("Error not in a sim")]
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ namespace SecondBotEvents.Services
|
|||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class FriendRequest
|
||||
{
|
||||
public UUID AgentID { get; set; }
|
||||
public string AgentName { get; set; }
|
||||
public UUID SessionID { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class DataStoreService : BotServices
|
||||
{
|
||||
protected new DataStoreConfig myConfig;
|
||||
|
|
@ -42,8 +50,9 @@ namespace SecondBotEvents.Services
|
|||
|
||||
protected Dictionary<UUID, string> groupsKey2Name = [];
|
||||
|
||||
protected Dictionary<UUID, List<UUID>> groupMembers = [];
|
||||
protected Dictionary<UUID, Dictionary<UUID, GroupMember>> groupMembers = [];
|
||||
protected Dictionary<UUID, Dictionary<UUID, GroupRole>> groupRoles = [];
|
||||
protected Dictionary<UUID, KeyValuePair<string, long>> loginTokens = []; // Avatar -> {Token, Expiry}
|
||||
|
||||
protected List<string> localChatHistory = [];
|
||||
|
||||
|
|
@ -57,6 +66,7 @@ namespace SecondBotEvents.Services
|
|||
|
||||
protected List<CommandHistory> commandHistories = [];
|
||||
protected List<GroupInvite> PendingGroupInvites = [];
|
||||
protected List<FriendRequest> PendingFriendRequests = [];
|
||||
|
||||
|
||||
|
||||
|
|
@ -393,6 +403,59 @@ namespace SecondBotEvents.Services
|
|||
}
|
||||
}
|
||||
|
||||
public string GetGroupMemberRank(UUID group, UUID avatar)
|
||||
{
|
||||
lock (groupMembers)
|
||||
{
|
||||
if (groupMembers.ContainsKey(group) == false || groupMembers[group].ContainsKey(avatar) == false)
|
||||
{
|
||||
LogFormater.Info($"[DEBUG] GetGroupMemberRank: Returning Guest. GroupKnown: {groupMembers.ContainsKey(group)}, AvatarKnown: {(groupMembers.ContainsKey(group) ? groupMembers[group].ContainsKey(avatar).ToString() : "N/A")} for Group: {group}, Avatar: {avatar}");
|
||||
return "Guest";
|
||||
}
|
||||
GroupMember m = groupMembers[group][avatar];
|
||||
if (m.IsOwner == true) return "Owner";
|
||||
if (m.Title != null && (m.Title.Contains("Officer") || m.Title.Contains("Officer"))) return "Officer";
|
||||
|
||||
// Also check roles if title didn't match
|
||||
if (groupRoles.ContainsKey(group))
|
||||
{
|
||||
// This is a bit complex as we need member-to-role mapping which we don't have cached yet
|
||||
// For now, Owner and Title check is a good start.
|
||||
}
|
||||
return "Member";
|
||||
}
|
||||
}
|
||||
|
||||
public string RequestLoginToken(UUID avatar)
|
||||
{
|
||||
string token = _delayRandom.Next(100000, 999999).ToString();
|
||||
long expiry = SecondbotHelpers.UnixTimeNow() + 300; // 5 mins
|
||||
lock (loginTokens)
|
||||
{
|
||||
loginTokens[avatar] = new KeyValuePair<string, long>(token, expiry);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public bool VerifyLoginToken(UUID avatar, string token)
|
||||
{
|
||||
lock (loginTokens)
|
||||
{
|
||||
if (loginTokens.ContainsKey(avatar) == false) return false;
|
||||
if (loginTokens[avatar].Value < SecondbotHelpers.UnixTimeNow())
|
||||
{
|
||||
loginTokens.Remove(avatar);
|
||||
return false;
|
||||
}
|
||||
if (loginTokens[avatar].Key == token)
|
||||
{
|
||||
loginTokens.Remove(avatar);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<UUID, string> GetGroupRoles(UUID group)
|
||||
{
|
||||
Dictionary<UUID, string> reply = [];
|
||||
|
|
@ -432,9 +495,9 @@ namespace SecondBotEvents.Services
|
|||
{
|
||||
if (groupMembers.ContainsKey(group) == false)
|
||||
{
|
||||
groupMembers.Add(group, []);
|
||||
groupMembers.Add(group, new Dictionary<UUID, GroupMember>());
|
||||
}
|
||||
if (groupMembers[group].Contains(avatar) == false)
|
||||
if (groupMembers[group].ContainsKey(avatar) == false)
|
||||
{
|
||||
reply = false;
|
||||
AutoResetEvent fetchEvent = new(false);
|
||||
|
|
@ -470,10 +533,10 @@ namespace SecondBotEvents.Services
|
|||
{
|
||||
if (groupMembers.ContainsKey(Group) == false)
|
||||
{
|
||||
groupMembers.Add(Group, []);
|
||||
groupMembers.Add(Group, new Dictionary<UUID, GroupMember>());
|
||||
}
|
||||
return groupMembers[Group].Keys.ToList();
|
||||
}
|
||||
return groupMembers[Group];
|
||||
}
|
||||
|
||||
public void BotRecordReplyIM(UUID sessionid,string message)
|
||||
|
|
@ -646,6 +709,41 @@ namespace SecondBotEvents.Services
|
|||
}
|
||||
}
|
||||
|
||||
public void AddPendingFriendRequest(UUID agentID, string agentName, UUID sessionID, string message)
|
||||
{
|
||||
lock (PendingFriendRequests)
|
||||
{
|
||||
// Avoid duplicates
|
||||
if (PendingFriendRequests.Any(r => r.AgentID == agentID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
PendingFriendRequests.Add(new FriendRequest
|
||||
{
|
||||
AgentID = agentID,
|
||||
AgentName = agentName,
|
||||
SessionID = sessionID,
|
||||
Message = message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public List<FriendRequest> GetPendingFriendRequests()
|
||||
{
|
||||
lock (PendingFriendRequests)
|
||||
{
|
||||
return [.. PendingFriendRequests];
|
||||
}
|
||||
}
|
||||
|
||||
public void RemovePendingFriendRequest(UUID agentID)
|
||||
{
|
||||
lock (PendingFriendRequests)
|
||||
{
|
||||
PendingFriendRequests.RemoveAll(r => r.AgentID == agentID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected int ObjectsAvatarsHash = 0;
|
||||
/*
|
||||
|
|
@ -967,14 +1065,18 @@ namespace SecondBotEvents.Services
|
|||
{
|
||||
lock (groupMembers)
|
||||
{
|
||||
if (groupMembers.ContainsKey(e.GroupID) == true)
|
||||
if (groupMembers.ContainsKey(e.GroupID) == false)
|
||||
{
|
||||
groupMembers.Remove(e.GroupID);
|
||||
groupMembers.Add(e.GroupID, new Dictionary<UUID, GroupMember>());
|
||||
}
|
||||
groupMembers.Add(e.GroupID, []);
|
||||
else
|
||||
{
|
||||
groupMembers[e.GroupID].Clear();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<UUID, GroupMember> a in e.Members)
|
||||
{
|
||||
groupMembers[e.GroupID].Add(a.Value.ID);
|
||||
groupMembers[e.GroupID][a.Key] = a.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1053,6 +1155,30 @@ namespace SecondBotEvents.Services
|
|||
}
|
||||
|
||||
Dictionary<UUID, Avatar.AvatarProperties> avprops = [];
|
||||
Dictionary<UUID, Dictionary<UUID, string>> avpicks = [];
|
||||
|
||||
public KeyValuePair<bool, Dictionary<UUID, string>> GetAvatarPicks(UUID agentID)
|
||||
{
|
||||
if (avpicks.ContainsKey(agentID) == false)
|
||||
{
|
||||
GetClient().Avatars.RequestAvatarPicks(agentID);
|
||||
return new KeyValuePair<bool, Dictionary<UUID, string>>(false, []);
|
||||
}
|
||||
return new KeyValuePair<bool, Dictionary<UUID, string>>(true, avpicks[agentID]);
|
||||
}
|
||||
|
||||
protected void AvatarPicksReply(object sender, AvatarPicksReplyEventArgs e)
|
||||
{
|
||||
lock (avpicks)
|
||||
{
|
||||
if (avpicks.ContainsKey(e.AvatarID) == false)
|
||||
{
|
||||
avpicks.Add(e.AvatarID, []);
|
||||
}
|
||||
avpicks[e.AvatarID] = e.Picks;
|
||||
}
|
||||
}
|
||||
|
||||
public KeyValuePair<bool,Avatar.AvatarProperties> GetAvatarAvatarProperties(UUID agentID)
|
||||
{
|
||||
if (avprops.ContainsKey(agentID) == false)
|
||||
|
|
@ -1087,6 +1213,7 @@ namespace SecondBotEvents.Services
|
|||
GetClient().Estate.EstateBansReply += EstateBans;
|
||||
GetClient().Friends.FriendFoundReply += FindFriendReply;
|
||||
GetClient().Avatars.AvatarPropertiesReply += AvatarDetailsReply;
|
||||
GetClient().Avatars.AvatarPicksReply += AvatarPicksReply;
|
||||
GetClient().Groups.RequestCurrentGroups();
|
||||
_ = GetClient().Self.RetrieveInstantMessages();
|
||||
if(myConfig.GetPrefetchInventory() == true)
|
||||
|
|
@ -1395,6 +1522,12 @@ namespace SecondBotEvents.Services
|
|||
}
|
||||
break;
|
||||
}
|
||||
case InstantMessageDialog.FriendshipOffered:
|
||||
{
|
||||
AddAvatar(e.IM.FromAgentID, e.IM.FromAgentName);
|
||||
AddPendingFriendRequest(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.IMSessionID, e.IM.Message);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using EmbedIO;
|
||||
using EmbedIO;
|
||||
using EmbedIO.Actions;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Utilities;
|
||||
|
|
@ -173,6 +173,7 @@ namespace SecondBotEvents.Services
|
|||
{
|
||||
protected EventsSecondBot master = setmaster;
|
||||
protected UUID avataruuid = UUID.Zero;
|
||||
protected string avatarname = "";
|
||||
protected Parcel targetparcel = null;
|
||||
|
||||
protected GridClient GetClient()
|
||||
|
|
@ -204,20 +205,54 @@ namespace SecondBotEvents.Services
|
|||
{
|
||||
if (UUID.TryParse(avatar, out avataruuid) == true)
|
||||
{
|
||||
// sent a UUID not a name, thats fine
|
||||
// try and load the name anyway
|
||||
master.DataStoreService.GetAvatarName(avataruuid);
|
||||
avatarname = master.DataStoreService.GetAvatarName(avataruuid);
|
||||
return;
|
||||
}
|
||||
avataruuid = UUID.Zero;
|
||||
string UUIDfetch = master.DataStoreService.GetAvatarUUID(avatar);
|
||||
if (UUIDfetch != "lookup")
|
||||
|
||||
avatarname = avatar;
|
||||
string UUIDfetch = master.DataStoreService.GetAvatarUUID(avatarname);
|
||||
if (UUID.TryParse(UUIDfetch, out avataruuid) == false)
|
||||
{
|
||||
if(UUID.TryParse(UUIDfetch, out avataruuid) == false)
|
||||
{
|
||||
avataruuid = UUID.Zero;
|
||||
}
|
||||
avataruuid = UUID.Zero;
|
||||
}
|
||||
|
||||
if (avataruuid == UUID.Zero)
|
||||
{
|
||||
avataruuid = ResolveAvatarUUID(avatarname);
|
||||
}
|
||||
}
|
||||
|
||||
protected UUID ResolveAvatarUUID(string avatar)
|
||||
{
|
||||
if (UUID.TryParse(avatar, out UUID result) == true) return result;
|
||||
|
||||
AutoResetEvent resolved = new(false);
|
||||
UUID queryID = UUID.Random();
|
||||
UUID resolvedUUID = UUID.Zero;
|
||||
EventHandler<AvatarPickerReplyEventArgs> callback = (sender, e) => {
|
||||
if (e.QueryID == queryID)
|
||||
{
|
||||
foreach (var kvp in e.Avatars)
|
||||
{
|
||||
if (kvp.Value.Equals(avatar, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resolvedUUID = kvp.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resolvedUUID == UUID.Zero && e.Avatars.Count > 0)
|
||||
{
|
||||
foreach (var kvp in e.Avatars) { resolvedUUID = kvp.Key; break; }
|
||||
}
|
||||
resolved.Set();
|
||||
}
|
||||
};
|
||||
|
||||
GetClient().Avatars.AvatarPickerReply += callback;
|
||||
GetClient().Avatars.RequestAvatarNameSearch(avatar, queryID);
|
||||
resolved.WaitOne(5000);
|
||||
GetClient().Avatars.AvatarPickerReply -= callback;
|
||||
return resolvedUUID;
|
||||
}
|
||||
|
||||
protected object BasicReply(string input)
|
||||
|
|
|
|||
Loading…
Reference in a new issue