~ruther/NosSmooth

e1ab9a1ac7bf75a062ffab6e946fd38bec557c9c — František Boháček 3 years ago f2aaa46
feat(chatcommands): add support for chat commands using Remora.Commands
A Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs => Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs +92 -0
@@ 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;

/// <summary>
/// Handles commands in the chat.
/// </summary>
public class ChatCommandInterceptor : IPacketInterceptor
{
    private readonly CommandService _commandService;
    private readonly IServiceProvider _serviceProvider;
    private readonly FeedbackService _feedbackService;
    private readonly ILogger<ChatCommandInterceptor> _logger;
    private readonly ChatCommandsOptions _options;

    /// <summary>
    /// Initializes a new instance of the <see cref="ChatCommandInterceptor"/> class.
    /// </summary>
    /// <param name="options">The options.</param>
    /// <param name="commandService">The command service.</param>
    /// <param name="serviceProvider">The services.</param>
    /// <param name="feedbackService">The feedback service.</param>
    /// <param name="logger">The logger.</param>
    public ChatCommandInterceptor
    (
        IOptions<ChatCommandsOptions> options,
        CommandService commandService,
        IServiceProvider serviceProvider,
        FeedbackService feedbackService,
        ILogger<ChatCommandInterceptor> logger
    )
    {
        _commandService = commandService;
        _serviceProvider = serviceProvider;
        _feedbackService = feedbackService;
        _logger = logger;
        _options = options.Value;
    }

    /// <inheritdoc />
    public bool InterceptSend(ref string packet)
    {
        ReadOnlySpan<char> 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;
    }

    /// <inheritdoc />
    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

A Local/NosSmooth.ChatCommands/ChatCommandsOptions.cs => Local/NosSmooth.ChatCommands/ChatCommandsOptions.cs +18 -0
@@ 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;

/// <summary>
/// Options for <see cref="ChatCommandInterceptor"/>.
/// </summary>
public class ChatCommandsOptions
{
    /// <summary>
    /// Gets or sets the command prefix.
    /// </summary>
    public string Prefix { get; set; } = "#";
}
\ No newline at end of file

A Local/NosSmooth.ChatCommands/FeedbackService.cs => Local/NosSmooth.ChatCommands/FeedbackService.cs +68 -0
@@ 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;

/// <summary>
/// Feedback for chat commands.
/// </summary>
public class FeedbackService
{
    private readonly INostaleClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="FeedbackService"/> class.
    /// </summary>
    /// <param name="client">The nostale client.</param>
    public FeedbackService(INostaleClient client)
    {
        _client = client;

    }

    /// <summary>
    /// Send message error.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendErrorMessageAsync(string message, CancellationToken ct = default)
        => SendMessageAsync(message, SayColor.Red, ct);

    /// <summary>
    /// Send message success.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendSuccessMessageAsync(string message, CancellationToken ct = default)
        => SendMessageAsync(message, SayColor.Green, ct);

    /// <summary>
    /// Send message info.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendInfoMessageAsync(string message, CancellationToken ct = default)
        => SendMessageAsync(message, SayColor.Default, ct);

    /// <summary>
    /// Send message with the given color.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="color">The color.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendMessageAsync(string message, SayColor color, CancellationToken ct = default)
        => _client.ReceivePacketAsync(new SayPacket(EntityType.Map, 0, color, message), ct);
}
\ No newline at end of file

A Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj => Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj +18 -0
@@ 0,0 1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
      <ProjectReference Include="..\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj" />
    </ItemGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
      <PackageReference Include="Remora.Commands" Version="9.0.0" />
    </ItemGroup>

</Project>

A Local/NosSmooth.ChatCommands/ServiceCollectionExtensions.cs => Local/NosSmooth.ChatCommands/ServiceCollectionExtensions.cs +36 -0
@@ 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;

/// <summary>
/// Extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds NosTale commands and the interceptor to execute commands with.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <param name="prefix">The prefix for the commands.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddNostaleChatCommands(this IServiceCollection serviceCollection, string prefix = "#")
    {
        serviceCollection
            .Configure<ChatCommandsOptions>((o) => o.Prefix = prefix);

        return serviceCollection
            .AddCommands()
            .Configure<LocalClientOptions>(o => o.AllowIntercept = true)
            .AddSingleton<FeedbackService>()
            .AddPacketInterceptor<ChatCommandInterceptor>();
    }
}
\ No newline at end of file

M NosSmooth.sln => NosSmooth.sln +15 -0
@@ 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}

Do not follow this link