From e1ab9a1ac7bf75a062ffab6e946fd38bec557c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Thu, 20 Jan 2022 19:25:55 +0100 Subject: [PATCH] feat(chatcommands): add support for chat commands using Remora.Commands --- .../ChatCommandInterceptor.cs | 92 +++++++++++++++++++ .../ChatCommandsOptions.cs | 18 ++++ .../NosSmooth.ChatCommands/FeedbackService.cs | 68 ++++++++++++++ .../NosSmooth.ChatCommands.csproj | 18 ++++ .../ServiceCollectionExtensions.cs | 36 ++++++++ NosSmooth.sln | 15 +++ 6 files changed, 247 insertions(+) create mode 100644 Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs create mode 100644 Local/NosSmooth.ChatCommands/ChatCommandsOptions.cs create mode 100644 Local/NosSmooth.ChatCommands/FeedbackService.cs create mode 100644 Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj create mode 100644 Local/NosSmooth.ChatCommands/ServiceCollectionExtensions.cs diff --git a/Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs b/Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs new file mode 100644 index 0000000..9758f7e --- /dev/null +++ b/Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs @@ -0,0 +1,92 @@ +// +// ChatCommandInterceptor.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reflection.Emit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NosSmooth.Core.Extensions; +using NosSmooth.LocalClient; +using NosSmooth.Packets.Enums; +using NosSmooth.Packets.Enums.Chat; +using NosSmooth.Packets.Server.Chat; +using Remora.Commands.Services; + +namespace NosSmooth.ChatCommands; + +/// +/// Handles commands in the chat. +/// +public class ChatCommandInterceptor : IPacketInterceptor +{ + private readonly CommandService _commandService; + private readonly IServiceProvider _serviceProvider; + private readonly FeedbackService _feedbackService; + private readonly ILogger _logger; + private readonly ChatCommandsOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// The options. + /// The command service. + /// The services. + /// The feedback service. + /// The logger. + public ChatCommandInterceptor + ( + IOptions options, + CommandService commandService, + IServiceProvider serviceProvider, + FeedbackService feedbackService, + ILogger logger + ) + { + _commandService = commandService; + _serviceProvider = serviceProvider; + _feedbackService = feedbackService; + _logger = logger; + _options = options.Value; + } + + /// + public bool InterceptSend(ref string packet) + { + ReadOnlySpan span = packet; + if (span.StartsWith("say ") && span.Slice(4).StartsWith(_options.Prefix)) + { + var command = span.Slice(4 + _options.Prefix.Length).ToString(); + Task.Run(async () => await ExecuteCommand(command)); + return false; + } + + return true; + } + + /// + public bool InterceptReceive(ref string packet) + { + return true; + } + + private async Task ExecuteCommand(string command) + { + var preparedResult = await _commandService.TryPrepareCommandAsync(command, _serviceProvider); + if (!preparedResult.IsSuccess) + { + _logger.LogError($"Could not prepare \"{command}\""); + _logger.LogResultError(preparedResult); + await _feedbackService.SendErrorMessageAsync($"Could not prepare the given command. {preparedResult.Error.Message}"); + } + + var executeResult = await _commandService.TryExecuteAsync(preparedResult.Entity, _serviceProvider); + if (!executeResult.IsSuccess) + { + _logger.LogError($"Could not execute \"{command}\""); + _logger.LogResultError(executeResult); + await _feedbackService.SendErrorMessageAsync($"Could not execute the given command. {executeResult.Error.Message}"); + } + } +} \ No newline at end of file diff --git a/Local/NosSmooth.ChatCommands/ChatCommandsOptions.cs b/Local/NosSmooth.ChatCommands/ChatCommandsOptions.cs new file mode 100644 index 0000000..38e8922 --- /dev/null +++ b/Local/NosSmooth.ChatCommands/ChatCommandsOptions.cs @@ -0,0 +1,18 @@ +// +// ChatCommandsOptions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace NosSmooth.ChatCommands; + +/// +/// Options for . +/// +public class ChatCommandsOptions +{ + /// + /// Gets or sets the command prefix. + /// + public string Prefix { get; set; } = "#"; +} \ No newline at end of file diff --git a/Local/NosSmooth.ChatCommands/FeedbackService.cs b/Local/NosSmooth.ChatCommands/FeedbackService.cs new file mode 100644 index 0000000..31ab98f --- /dev/null +++ b/Local/NosSmooth.ChatCommands/FeedbackService.cs @@ -0,0 +1,68 @@ +// +// FeedbackService.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NosSmooth.Core.Client; +using NosSmooth.Packets.Enums; +using NosSmooth.Packets.Enums.Chat; +using NosSmooth.Packets.Server.Chat; +using Remora.Results; + +namespace NosSmooth.ChatCommands; + +/// +/// Feedback for chat commands. +/// +public class FeedbackService +{ + private readonly INostaleClient _client; + + /// + /// Initializes a new instance of the class. + /// + /// The nostale client. + public FeedbackService(INostaleClient client) + { + _client = client; + + } + + /// + /// Send message error. + /// + /// The message to send. + /// The cancellation token for cancelling the operation. + /// A result that may or may not have succeeded. + public Task SendErrorMessageAsync(string message, CancellationToken ct = default) + => SendMessageAsync(message, SayColor.Red, ct); + + /// + /// Send message success. + /// + /// The message to send. + /// The cancellation token for cancelling the operation. + /// A result that may or may not have succeeded. + public Task SendSuccessMessageAsync(string message, CancellationToken ct = default) + => SendMessageAsync(message, SayColor.Green, ct); + + /// + /// Send message info. + /// + /// The message to send. + /// The cancellation token for cancelling the operation. + /// A result that may or may not have succeeded. + public Task SendInfoMessageAsync(string message, CancellationToken ct = default) + => SendMessageAsync(message, SayColor.Default, ct); + + /// + /// Send message with the given color. + /// + /// The message to send. + /// The color. + /// The cancellation token for cancelling the operation. + /// A result that may or may not have succeeded. + public Task SendMessageAsync(string message, SayColor color, CancellationToken ct = default) + => _client.ReceivePacketAsync(new SayPacket(EntityType.Map, 0, color, message), ct); +} \ No newline at end of file diff --git a/Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj b/Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj new file mode 100644 index 0000000..5cad30c --- /dev/null +++ b/Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/Local/NosSmooth.ChatCommands/ServiceCollectionExtensions.cs b/Local/NosSmooth.ChatCommands/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f5e8f1a --- /dev/null +++ b/Local/NosSmooth.ChatCommands/ServiceCollectionExtensions.cs @@ -0,0 +1,36 @@ +// +// ServiceCollectionExtensions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.DependencyInjection; +using NosSmooth.LocalClient; +using NosSmooth.LocalClient.Extensions; +using Remora.Commands.Extensions; + +namespace NosSmooth.ChatCommands; + +/// +/// Extension methods for . +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds NosTale commands and the interceptor to execute commands with. + /// + /// The service collection. + /// The prefix for the commands. + /// The collection. + public static IServiceCollection AddNostaleChatCommands(this IServiceCollection serviceCollection, string prefix = "#") + { + serviceCollection + .Configure((o) => o.Prefix = prefix); + + return serviceCollection + .AddCommands() + .Configure(o => o.AllowIntercept = true) + .AddSingleton() + .AddPacketInterceptor(); + } +} \ No newline at end of file diff --git a/NosSmooth.sln b/NosSmooth.sln index c7b05f4..4c9606e 100644 --- a/NosSmooth.sln +++ b/NosSmooth.sln @@ -62,6 +62,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Packets", "Packet EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.PacketSerializer.Abstractions", "Packets\NosSmooth.PacketSerializer.Abstractions\NosSmooth.PacketSerializer.Abstractions.csproj", "{CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.ChatCommands", "Local\NosSmooth.ChatCommands\NosSmooth.ChatCommands.csproj", "{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -312,6 +314,18 @@ Global {CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}.Release|x64.Build.0 = Release|Any CPU {CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}.Release|x86.ActiveCfg = Release|Any CPU {CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}.Release|x86.Build.0 = Release|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x64.Build.0 = Debug|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x86.Build.0 = Debug|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|Any CPU.Build.0 = Release|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x64.ActiveCfg = Release|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x64.Build.0 = Release|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x86.ActiveCfg = Release|Any CPU + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -337,6 +351,7 @@ Global {C4DAFD83-C6DC-4597-AA1F-BA2F3ABB612C} = {54A49AC2-55B3-4156-8023-41C56719EBB5} {86B4ED0C-CD28-4C6C-B58E-B4B1F7AAD683} = {54A49AC2-55B3-4156-8023-41C56719EBB5} {CF03BCEA-EB5B-427F-8576-7DA7EB869BDC} = {54A49AC2-55B3-4156-8023-41C56719EBB5} + {7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C5F46653-4DEC-429B-8580-4ED18ED9B4CA} -- 2.49.0