From bb03b68c17c828ffb87af67676628ba9f527f42c Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sat, 26 Aug 2023 12:45:30 +0100 Subject: [PATCH] feat: replace !who with /who slash command --- VpBridge/Commands/WhoCommand.cs | 73 ++++++++++++++++++++++++ VpBridge/Program.cs | 10 +++- VpBridge/Services/DiscordService.cs | 88 ++++++++++++++++++----------- VpBridge/VpBridge.csproj | 1 + 4 files changed, 136 insertions(+), 36 deletions(-) create mode 100644 VpBridge/Commands/WhoCommand.cs diff --git a/VpBridge/Commands/WhoCommand.cs b/VpBridge/Commands/WhoCommand.cs new file mode 100644 index 0000000..e1c4d1d --- /dev/null +++ b/VpBridge/Commands/WhoCommand.cs @@ -0,0 +1,73 @@ +using Cysharp.Text; +using Discord; +using Discord.Interactions; +using VpSharp; +using VpSharp.Entities; + +namespace VpBridge.Commands; + +/// +/// Represents a class which implements the who command. +/// +internal sealed class WhoCommand : InteractionModuleBase +{ + private readonly VirtualParadiseClient _virtualParadiseClient; + + /// + /// Initializes a new instance of the class. + /// + /// The Virtual Paradise client. + 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()); + } +} diff --git a/VpBridge/Program.cs b/VpBridge/Program.cs index 49e4679..b63efc9 100644 --- a/VpBridge/Program.cs +++ b/VpBridge/Program.cs @@ -1,4 +1,5 @@ -using Discord; +using Discord; +using Discord.Interactions; using Discord.WebSocket; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -24,10 +25,13 @@ builder.Logging.ClearProviders(); builder.Logging.AddSerilog(); builder.Services.AddSingleton(); -builder.Services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(new DiscordSocketConfig { GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent -})); +}); builder.Services.AddHostedSingleton(); builder.Services.AddHostedSingleton(); diff --git a/VpBridge/Services/DiscordService.cs b/VpBridge/Services/DiscordService.cs index f75c9c0..52c8669 100644 --- a/VpBridge/Services/DiscordService.cs +++ b/VpBridge/Services/DiscordService.cs @@ -1,12 +1,14 @@ -using System.Reactive.Linq; +using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text; using System.Text.RegularExpressions; using Discord; +using Discord.Interactions; using Discord.WebSocket; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using VpBridge.Commands; using VpSharp; using VpSharp.Entities; @@ -19,7 +21,9 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic private static readonly Regex EscapeRegex = GetEscapeRegex(); private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; private readonly IConfiguration _configuration; + private readonly InteractionService _interactionService; private readonly DiscordSocketClient _discordClient; private readonly VirtualParadiseClient _virtualParadiseClient; private readonly Subject _messageReceived = new(); @@ -28,16 +32,22 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic /// Initializes a new instance of the class. /// /// The logger. + /// The service provider. /// The configuration. + /// The interaction service. /// The Discord client. /// The Virtual Paradise client. public DiscordService(ILogger logger, + IServiceProvider serviceProvider, IConfiguration configuration, + InteractionService interactionService, DiscordSocketClient discordClient, VirtualParadiseClient virtualParadiseClient) { _logger = logger; + _serviceProvider = serviceProvider; _configuration = configuration; + _interactionService = interactionService; _discordClient = discordClient; _virtualParadiseClient = virtualParadiseClient; } @@ -49,40 +59,13 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _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()) - return Task.CompletedTask; + _logger.LogInformation("Adding command modules"); + await _interactionService.AddModuleAsync(_serviceProvider).ConfigureAwait(false); - if (message.Content.Equals("!who")) - { - VirtualParadiseAvatar[] avatars = _virtualParadiseClient.Avatars.Where(a => !a.IsBot).ToArray(); - 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; - }; + _discordClient.Ready += OnReady; + _discordClient.InteractionCreated += OnInteractionCreated; + _discordClient.MessageReceived += OnDiscordMessageReceived; string token = _configuration.GetSection("Discord:Token").Value ?? throw new InvalidOperationException("Token is not set."); @@ -92,6 +75,45 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic 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()) + 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(); + } + /// public Task SendMessageAsync(VirtualParadiseMessage message) { diff --git a/VpBridge/VpBridge.csproj b/VpBridge/VpBridge.csproj index e61747e..a302290 100644 --- a/VpBridge/VpBridge.csproj +++ b/VpBridge/VpBridge.csproj @@ -52,6 +52,7 @@ +