diff --git a/TPP.Core/Chat/ICommandHandler.cs b/TPP.Core/Chat/ICommandHandler.cs new file mode 100644 index 00000000..ff205e35 --- /dev/null +++ b/TPP.Core/Chat/ICommandHandler.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace TPP.Core.Chat; + +public interface ICommandHandler +{ + public Task ProcessIncomingMessage(IChat chat, Message message); +} diff --git a/TPP.Core/Modes/ModeBase.cs b/TPP.Core/Modes/ModeBase.cs index 9d8fcb90..27ea0996 100644 --- a/TPP.Core/Modes/ModeBase.cs +++ b/TPP.Core/Modes/ModeBase.cs @@ -22,7 +22,7 @@ namespace TPP.Core.Modes { - public sealed class ModeBase : IWithLifecycle + public sealed class ModeBase : IWithLifecycle, ICommandHandler { private static readonly Role[] ExemptionRoles = { Role.Operator, Role.Moderator, Role.ModbotExempt }; @@ -83,7 +83,7 @@ public ModeBase( c => Setups.SetUpCommandProcessor(loggerFactory, baseConfig, argsParser, repos, stopToken, muteInputsToken, messageSender: c, chatModeChanger: c as IChatModeChanger, executor: c as IExecutor, - pokedexData.KnownSpecies, transmuter)); + pokedexData.KnownSpecies, transmuter, this)); _outgoingMessagequeueRepo = repos.OutgoingMessagequeueRepo; _messagelogRepo = repos.MessagelogRepo; @@ -170,7 +170,7 @@ private void MessageReceived(object? sender, MessageEventArgs e) private static readonly CaseInsensitiveImmutableHashSet CoStreamAllowedCommands = new(["left", "right"]); - private async Task ProcessIncomingMessage(IChat chat, Message message) + public async Task ProcessIncomingMessage(IChat chat, Message message) { Instant now = _clock.GetCurrentInstant(); await _messagelogRepo.LogChat( diff --git a/TPP.Core/Setups.cs b/TPP.Core/Setups.cs index f1e0b550..a4bab263 100644 --- a/TPP.Core/Setups.cs +++ b/TPP.Core/Setups.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -105,7 +106,8 @@ public static CommandProcessor SetUpCommandProcessor( IChatModeChanger? chatModeChanger, IExecutor? executor, IImmutableSet knownSpecies, - ITransmuter transmuter) + ITransmuter transmuter, + ICommandHandler commandHandler) { var commandProcessor = new CommandProcessor( loggerFactory.CreateLogger(), @@ -170,7 +172,8 @@ public static CommandProcessor SetUpCommandProcessor( commandProcessor.InstallCommand(command); } SetUpDynamicCommands(loggerFactory.CreateLogger("setups"), commandProcessor, databases.ResponseCommandRepo); - SetUpDynamicAliases(loggerFactory.CreateLogger("setups"), commandProcessor, databases.CommandAliasRepo); + SetUpDynamicAliases(loggerFactory.CreateLogger("setups"), commandProcessor, databases.CommandAliasRepo, + commandHandler); return commandProcessor; } @@ -315,7 +318,8 @@ void UninstallCommand(string commandName) } private static void SetUpDynamicAliases( - ILogger logger, CommandProcessor commandProcessor, ICommandAliasRepo commandAliasRepo) + ILogger logger, CommandProcessor commandProcessor, ICommandAliasRepo commandAliasRepo, + ICommandHandler commandHandler) { IImmutableList aliases = commandAliasRepo.GetAliases().Result; @@ -323,26 +327,56 @@ private static void SetUpDynamicAliases( void InstallAlias(CommandAlias alias) { Command? existing = commandProcessor.FindCommand(alias.Alias); - Command? targetCommand = commandProcessor.FindCommand(alias.TargetCommand); if (existing != null) { logger.LogWarning( "not installing dynamic command alias '{Command}' " + "because it conflicts with an existing command", alias.Alias); + return; } - else if (targetCommand == null) - { - logger.LogWarning( - "not installing dynamic command alias '{Command}' " + - "because the target command '{TargetCommand}' does not exist", alias.Alias, alias.TargetCommand); - } - else - { - var aliasCommand = new Command(alias.Alias, targetCommand.Value.Execution) - { Description = targetCommand.Value.Description }; - commandProcessor.InstallCommand(aliasCommand.WithFixedArgs(alias.FixedArgs)); - dynamicallyInstalledAliases.Add(alias.Alias); - } + // If we didn't want to be able to also alias to old-core commands, we could just look up the target + // command and execute it directly, like this: + // + // Command? targetCommand = commandProcessor.FindCommand(alias.TargetCommand); + // if (targetCommand == null) + // { + // logger.LogWarning( + // "not installing dynamic command alias '{Command}' " + + // "because the target command '{TargetCommand}' does not exist", alias.Alias, alias.TargetCommand); + // return; + // } + // var aliasCommand = new Command(alias.Alias, targetCommand.Value.Execution) + // { Description = targetCommand.Value.Description }; + // commandProcessor.InstallCommand(aliasCommand.WithFixedArgs(alias.FixedArgs)); + // dynamicallyInstalledAliases.Add(alias.Alias); + // + // Unfortunately, we do want old-core support, so we have to take a lengthy detour: + // Assemble a brand-new message that has the alias resolved manually, + // and run that through the entire processing chain. + + string aliasReplacement = string.Join(' ', [alias.TargetCommand, ..alias.FixedArgs]); + var aliasCommand = new Command(alias.Alias, async ctx => + { + string messageTextWithAliasResolved = + new Regex("(?<=!?)" + Regex.Escape(alias.Alias), RegexOptions.IgnoreCase) + .Replace(ctx.Message.MessageText, aliasReplacement, 1); + string ircTextWithAliasResolved = + new Regex("(?<=.*? (?:PRIVMSG|WHISPER) .*? :!?)" + Regex.Escape(alias.Alias), RegexOptions.IgnoreCase) + .Replace(ctx.Message.RawIrcMessage, aliasReplacement, 1); + + var messageWithAliasResolved = new Message( + ctx.Message.User, + messageTextWithAliasResolved, + ctx.Message.MessageSource, + ircTextWithAliasResolved + ); + await commandHandler.ProcessIncomingMessage(ctx.Source!, messageWithAliasResolved); + return new CommandResult(); + }) + { Description = "Alias for: " + aliasReplacement }; + + commandProcessor.InstallCommand(aliasCommand); + dynamicallyInstalledAliases.Add(alias.Alias); } void UninstallAlias(string aliasName) {