feat: replace !who with /who slash command

This commit is contained in:
Oliver Booth 2023-08-26 12:45:30 +01:00
parent a1086a834c
commit bb03b68c17
Signed by: oliverbooth
GPG Key ID: B89D139977693FED
4 changed files with 136 additions and 36 deletions

View File

@ -0,0 +1,73 @@
using Cysharp.Text;
using Discord;
using Discord.Interactions;
using VpSharp;
using VpSharp.Entities;
namespace VpBridge.Commands;
/// <summary>
/// Represents a class which implements the <c>who</c> command.
/// </summary>
internal sealed class WhoCommand : InteractionModuleBase<SocketInteractionContext>
{
private readonly VirtualParadiseClient _virtualParadiseClient;
/// <summary>
/// Initializes a new instance of the <see cref="WhoCommand" /> class.
/// </summary>
/// <param name="virtualParadiseClient">The Virtual Paradise client.</param>
public WhoCommand(VirtualParadiseClient virtualParadiseClient)
{
_virtualParadiseClient = virtualParadiseClient;
}
[SlashCommand("who", "Displays a list of active users in Virtual Paradise.")]
[RequireContext(ContextType.Guild)]
public async Task HandleAsync()
{
var embed = new EmbedBuilder();
embed.WithColor(0x1E88E5);
embed.WithAuthor($"🌎 {_virtualParadiseClient.CurrentWorld?.Name}");
embed.WithTitle("Active Users");
embed.WithTimestamp(DateTimeOffset.UtcNow);
using Utf8ValueStringBuilder userBuilder = ZString.CreateUtf8StringBuilder();
using Utf8ValueStringBuilder botsBuilder = ZString.CreateUtf8StringBuilder();
var userCount = 0;
var botCount = 0;
foreach (VirtualParadiseAvatar avatar in _virtualParadiseClient.Avatars)
{
if (avatar.IsBot)
{
botsBuilder.AppendLine($"* {avatar.Name} ({avatar.Session})");
botCount++;
}
else
{
userBuilder.AppendLine($"* {avatar.Name} ({avatar.Session})");
userCount++;
}
}
string userTitle = userCount switch
{
0 => "Users",
1 => "1 User",
_ => $"{userCount} Users"
};
string botTitle = botCount switch
{
0 => "Bots",
1 => "1 Bot",
_ => $"{botCount} Bots"
};
embed.AddField($"👤 {userTitle}", userCount > 0 ? userBuilder.ToString() : "*None*", true);
embed.AddField($"🤖 {botTitle}", botCount > 0 ? botsBuilder.ToString() : "*None*", true);
await RespondAsync(embed: embed.Build());
}
}

View File

@ -1,4 +1,5 @@
using Discord; using Discord;
using Discord.Interactions;
using Discord.WebSocket; using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -24,10 +25,13 @@ builder.Logging.ClearProviders();
builder.Logging.AddSerilog(); builder.Logging.AddSerilog();
builder.Services.AddSingleton<VirtualParadiseClient>(); builder.Services.AddSingleton<VirtualParadiseClient>();
builder.Services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig
builder.Services.AddSingleton<InteractionService>();
builder.Services.AddSingleton<DiscordSocketClient>();
builder.Services.AddSingleton(new DiscordSocketConfig
{ {
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent
})); });
builder.Services.AddHostedSingleton<IVirtualParadiseService, VirtualParadiseService>(); builder.Services.AddHostedSingleton<IVirtualParadiseService, VirtualParadiseService>();
builder.Services.AddHostedSingleton<IDiscordService, DiscordService>(); builder.Services.AddHostedSingleton<IDiscordService, DiscordService>();

View File

@ -1,12 +1,14 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Discord; using Discord;
using Discord.Interactions;
using Discord.WebSocket; using Discord.WebSocket;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using VpBridge.Commands;
using VpSharp; using VpSharp;
using VpSharp.Entities; using VpSharp.Entities;
@ -19,7 +21,9 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
private static readonly Regex EscapeRegex = GetEscapeRegex(); private static readonly Regex EscapeRegex = GetEscapeRegex();
private readonly ILogger<DiscordService> _logger; private readonly ILogger<DiscordService> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly InteractionService _interactionService;
private readonly DiscordSocketClient _discordClient; private readonly DiscordSocketClient _discordClient;
private readonly VirtualParadiseClient _virtualParadiseClient; private readonly VirtualParadiseClient _virtualParadiseClient;
private readonly Subject<IUserMessage> _messageReceived = new(); private readonly Subject<IUserMessage> _messageReceived = new();
@ -28,16 +32,22 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
/// Initializes a new instance of the <see cref="DiscordService" /> class. /// Initializes a new instance of the <see cref="DiscordService" /> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="interactionService">The interaction service.</param>
/// <param name="discordClient">The Discord client.</param> /// <param name="discordClient">The Discord client.</param>
/// <param name="virtualParadiseClient">The Virtual Paradise client.</param> /// <param name="virtualParadiseClient">The Virtual Paradise client.</param>
public DiscordService(ILogger<DiscordService> logger, public DiscordService(ILogger<DiscordService> logger,
IServiceProvider serviceProvider,
IConfiguration configuration, IConfiguration configuration,
InteractionService interactionService,
DiscordSocketClient discordClient, DiscordSocketClient discordClient,
VirtualParadiseClient virtualParadiseClient) VirtualParadiseClient virtualParadiseClient)
{ {
_logger = logger; _logger = logger;
_serviceProvider = serviceProvider;
_configuration = configuration; _configuration = configuration;
_interactionService = interactionService;
_discordClient = discordClient; _discordClient = discordClient;
_virtualParadiseClient = virtualParadiseClient; _virtualParadiseClient = virtualParadiseClient;
} }
@ -49,40 +59,13 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
_logger.LogInformation("Establishing relay"); _logger.LogInformation("Establishing relay");
_discordClient.MessageReceived += arg =>
{
if (arg is not IUserMessage message)
return Task.CompletedTask;
if (message.Channel.Id != _configuration.GetSection("Discord:ChannelId").Get<ulong>()) _logger.LogInformation("Adding command modules");
return Task.CompletedTask; await _interactionService.AddModuleAsync<WhoCommand>(_serviceProvider).ConfigureAwait(false);
if (message.Content.Equals("!who")) _discordClient.Ready += OnReady;
{ _discordClient.InteractionCreated += OnInteractionCreated;
VirtualParadiseAvatar[] avatars = _virtualParadiseClient.Avatars.Where(a => !a.IsBot).ToArray(); _discordClient.MessageReceived += OnDiscordMessageReceived;
int count = avatars.Length;
if (count > 0)
{
var builder = new StringBuilder();
builder.AppendLine("**Users In World 🌎**");
foreach (VirtualParadiseAvatar avatar in _virtualParadiseClient.Avatars)
{
if (avatar.IsBot || avatar == _virtualParadiseClient.CurrentAvatar)
continue;
builder.AppendLine($"• {avatar.Name}");
}
return message.ReplyAsync(builder.ToString());
}
return message.ReplyAsync("**No Users In World 🚫**");
}
_messageReceived.OnNext(message);
return Task.CompletedTask;
};
string token = _configuration.GetSection("Discord:Token").Value ?? string token = _configuration.GetSection("Discord:Token").Value ??
throw new InvalidOperationException("Token is not set."); throw new InvalidOperationException("Token is not set.");
@ -92,6 +75,45 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
await _discordClient.StartAsync(); await _discordClient.StartAsync();
} }
private Task OnDiscordMessageReceived(SocketMessage arg)
{
if (arg is not IUserMessage message)
return Task.CompletedTask;
if (message.Channel.Id != _configuration.GetSection("Discord:ChannelId").Get<ulong>())
return Task.CompletedTask;
_messageReceived.OnNext(message);
return Task.CompletedTask;
}
private async Task OnInteractionCreated(SocketInteraction interaction)
{
try
{
var context = new SocketInteractionContext(_discordClient, interaction);
IResult result = await _interactionService.ExecuteCommandAsync(context, _serviceProvider);
if (!result.IsSuccess)
switch (result.Error)
{
case InteractionCommandError.UnmetPrecondition:
break;
}
}
catch
{
if (interaction.Type is InteractionType.ApplicationCommand)
await interaction.GetOriginalResponseAsync().ContinueWith(async msg => await msg.Result.DeleteAsync());
}
}
private Task OnReady()
{
_logger.LogInformation("Discord client ready");
return _interactionService.RegisterCommandsGloballyAsync();
}
/// <inheritdoc /> /// <inheritdoc />
public Task SendMessageAsync(VirtualParadiseMessage message) public Task SendMessageAsync(VirtualParadiseMessage message)
{ {

View File

@ -52,6 +52,7 @@
<PackageReference Include="VpSharp" Version="0.1.0-nightly.43"/> <PackageReference Include="VpSharp" Version="0.1.0-nightly.43"/>
<PackageReference Include="X10D" Version="3.3.1"/> <PackageReference Include="X10D" Version="3.3.1"/>
<PackageReference Include="X10D.Hosting" Version="3.3.1"/> <PackageReference Include="X10D.Hosting" Version="3.3.1"/>
<PackageReference Include="ZString" Version="2.5.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>