hermes-client/Chat/Commands/CommandManager.cs

142 lines
6.5 KiB
C#
Raw Permalink Normal View History

using System.Text.RegularExpressions;
using CommonSocketLibrary.Abstract;
using CommonSocketLibrary.Common;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using TwitchChatTTS.Chat.Commands.Limits;
using TwitchChatTTS.Chat.Groups.Permissions;
using TwitchChatTTS.Hermes.Socket;
using TwitchChatTTS.Twitch.Socket.Messages;
using static TwitchChatTTS.Chat.Commands.TTSCommands;
namespace TwitchChatTTS.Chat.Commands
{
public class CommandManager : ICommandManager
{
private readonly User _user;
private ICommandSelector _commandSelector;
private readonly HermesSocketClient _hermes;
//private readonly TwitchWebsocketClient _twitch;
private readonly IGroupPermissionManager _permissionManager;
private readonly IUsagePolicy<long> _permissionPolicy;
private readonly ILogger _logger;
private string CommandStartSign { get; } = "!";
public CommandManager(
User user,
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
//[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
IGroupPermissionManager permissionManager,
IUsagePolicy<long> limitManager,
ILogger logger
)
{
_user = user;
_hermes = (hermes as HermesSocketClient)!;
//_twitch = (twitch as TwitchWebsocketClient)!;
_permissionManager = permissionManager;
_permissionPolicy = limitManager;
_logger = logger;
}
public async Task<ChatCommandResult> Execute(string arg, ChannelChatMessage message, IEnumerable<string> groups)
{
if (string.IsNullOrWhiteSpace(arg))
return ChatCommandResult.Unknown;
arg = arg.Trim();
if (!arg.StartsWith(CommandStartSign))
return ChatCommandResult.Unknown;
if (message.BroadcasterUserId != _user.TwitchUserId.ToString())
return ChatCommandResult.OtherRoom;
string[] parts = Regex.Matches(arg.Substring(CommandStartSign.Length), "(?<match>[^\"\\n\\s]+|\"[^\"\\n]*\")")
.Cast<Match>()
.Select(m => m.Groups["match"].Value)
.Where(m => !string.IsNullOrEmpty(m))
.Select(m => m.StartsWith('"') && m.EndsWith('"') ? m.Substring(1, m.Length - 2) : m)
.ToArray();
string[] args = parts.ToArray();
string com = args.First().ToLower();
CommandSelectorResult selectorResult = _commandSelector.GetBestMatch(args, message.Message.Fragments);
if (selectorResult.Command == null)
{
_logger.Warning($"Could not match '{arg}' to any command [chatter: {message.ChatterUserLogin}][chatter id: {message.ChatterUserId}]");
return ChatCommandResult.Missing;
}
// Check if command can be executed by this chatter.
var command = selectorResult.Command;
long chatterId = long.Parse(message.ChatterUserId);
var path = $"tts.commands.{com}";
if (chatterId != _user.OwnerId)
{
bool executable = command.AcceptCustomPermission ? CanExecute(chatterId, groups, path, selectorResult.Permissions) : false;
if (!executable)
{
2024-08-06 16:32:02 -04:00
_logger.Warning($"Denied permission to use command [chatter id: {chatterId}][args: {arg}][command type: {command.GetType().Name}]");
return ChatCommandResult.Permission;
}
}
if (!_permissionPolicy.TryUse(chatterId, groups, path))
{
_logger.Warning($"Chatter reached usage limit on command [command type: {command.GetType().Name}][chatter id: {chatterId}][path: {path}][groups: {string.Join("|", groups)}]");
return ChatCommandResult.RateLimited;
}
// Check if the arguments are valid.
var arguments = _commandSelector.GetNonStaticArguments(args, selectorResult.Path);
foreach (var entry in arguments)
{
var parameter = entry.Value;
var argument = entry.Key;
// Optional parameters were validated while fetching this command.
if (!parameter.Optional && !parameter.Validate(argument, message.Message.Fragments))
{
_logger.Warning($"Command failed due to an argument being invalid [argument name: {parameter.Name}][argument value: {argument}][parameter type: {parameter.GetType().Name}][arguments: {arg}][command type: {command.GetType().Name}][chatter: {message.ChatterUserLogin}][chatter id: {message.ChatterUserId}]");
return ChatCommandResult.Syntax;
}
}
var values = arguments.ToDictionary(d => d.Value.Name, d => d.Key);
try
{
await command.Execute(values, message, _hermes);
}
catch (Exception e)
{
_logger.Error(e, $"Command '{arg}' failed [args: {arg}][command type: {command.GetType().Name}][chatter: {message.ChatterUserLogin}][chatter id: {message.ChatterUserId}]");
return ChatCommandResult.Fail;
}
_logger.Information($"Executed the {com} command [args: {arg}][command type: {command.GetType().Name}][chatter: {message.ChatterUserLogin}][chatter id: {message.ChatterUserId}]");
return ChatCommandResult.Success;
}
public void Update(ICommandFactory factory)
{
_commandSelector = factory.Build();
}
private bool CanExecute(long chatterId, IEnumerable<string> groups, string path, string[]? additionalPaths)
{
_logger.Debug($"Checking for permission [chatter id: {chatterId}][group: {string.Join(", ", groups)}][path: {path}]{(additionalPaths != null ? "[paths: " + string.Join('|', additionalPaths) + "]" : string.Empty)}");
2024-08-06 16:32:02 -04:00
if (_permissionManager.CheckIfAllowed(groups, path) == true)
{
if (additionalPaths == null)
return true;
// All direct allow must not be false and at least one of them must be true.
if (additionalPaths.All(p => _permissionManager.CheckIfDirectAllowed(groups, p) != false) && additionalPaths.Any(p => _permissionManager.CheckIfDirectAllowed(groups, p) == true))
return true;
}
return false;
}
}
}