diff --git a/docs/guides/commands/post-execution.md b/docs/guides/commands/post-execution.md index 86a97a320..26a5937af 100644 --- a/docs/guides/commands/post-execution.md +++ b/docs/guides/commands/post-execution.md @@ -3,43 +3,149 @@ uid: Guides.Commands.PostExecution title: Post-command Execution Handling --- +> [!WARNING] +> This page is still under construction! + # Preface When developing a command system or modules, you may want to consider -building a post-execution handling system so you can have a finer -control over commands. Discord.NET offers several different +building a post-execution handling system so you can have a finer +control over commands. Discord.NET offers several different post-execution workflow for you to work with. -If you recall, in the [Command Guide], we've shown the following +If you recall, in the [Command Guide], we've shown the following example for executing and handling commands, [!code[Command Handler](samples/command_handler.cs)] -You may notice that after we perform [ExecuteAsync], we store the +You may notice that after we perform [ExecuteAsync], we store the result and print it to the chat. This is essentially the most basic post-execution handling. With this in mind, we could start doing things like the following, [!code[Basic Command Handler](samples/post-execution_basic.cs)] -**But!** This may not always be preferred, because you are -creating your post-execution logic *with* the essential command -handler. This could lead to messy code and has another potential +**But!** This may not always be preferred, because you are +creating your post-execution logic *with* the essential command +handler. This could lead to messy code and has another potential issue, working with `RunMode.Async`. -If your command is marked with `RunMode.Async`, [ExecuteAsync] will -return a successful [ExecuteResult] instead of whatever results -the actual command may return. Because of the way `RunMode.Async` -[works](xref:FAQ.Commands), handling within the command handler +If your command is marked with `RunMode.Async`, [ExecuteAsync] will +return a successful [ExecuteResult] instead of whatever results +the actual command may return. Because of the way `RunMode.Async` +[works](xref:FAQ.Commands), handling within the command handler may not always achieve the desired effect. ## CommandExecuted Event -Enter [CommandExecuted], an event that was introduced in -Discord.NET 2.0. This event is raised **when the command is -sucessfully executed** and is not prone to `RunMode.Async`'s -[ExecuteAsync] drawbacks. +Enter [CommandExecuted], an event that was introduced in +Discord.NET 2.0. This event is raised when the command is +sucessfully executed **without any runtime exceptions** (more on this +later). This means this event can be used to streamline your +post-execution design, and the best thing about this event is that it +is not prone to `RunMode.Async`'s [ExecuteAsync] drawbacks. + +With that in mind, we can begin working on code such as: + +```cs +public async Task SetupAsync() +{ + await _command.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + // Hook the execution event + _command.CommandExecuted += OnCommandExecutedAsync; + // Hook the command handler + _client.MessageReceived += HandleCommandAsync; +} +public async Task OnCommandExecutedAsync(CommandInfo command, ICommandContext context, IResult result) +{ + // We have access to the information of the command executed, + // the context of the command, and the result returned from the + // execution in this event. + + // We can tell the user what went wrong + if (!string.IsNullOrEmpty(result?.ErrorReason)) + { + await context.Channel.SendMessageAsync(result.ErrorReason); + } + + // ...or even log the result (the method used should fit into + // your existing log handler) + await _log.LogAsync(new LogMessage(LogSeverity.Info, "CommandExecution", $"{command.Name} was executed at {DateTime.UtcNow}.")); +} +public async Task HandleCommandAsync(SocketMessage msg) +{ + // Notice how clean our new command handler has become. + var message = messageParam as SocketUserMessage; + if (message == null) return; + int argPos = 0; + if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return; + var context = new SocketCommandContext(_client, message); + await _commands.ExecuteAsync(context, argPos, _services); +} +``` + +So now we have a streamlined post-execution pipeline, great! What's +next? We can take this further by using [RuntimeResult]. + +### RuntimeResult + +This class was introduced in 1.0, but it wasn't widely adopted due to +the previously mentioned [ExecuteAsync] drawback. Since we now have +access to proper result handling via the [CommandExecuted] event, +we can start making use of this class. + +#### What is it? + +`RuntimeResult` was introduced to allow developers to centralize +their command result logic. It is a result type that is designed to +be returned when the command has finished its logic. + +#### How to make use of it? + +The best way to make use of it is to create your own version of +`RuntimeResult`. You can achieve this by inheriting the `RuntimeResult` +class. + +```cs +public class MyCustomResult : RuntimeResult +{ +} +``` + +// todo: finish this section + +## CommandService.Log Event + +We have so far covered the handling of various result types, but we +haven't talked about what to do if the command enters a catastrophic +failure (i.e. exceptions). To resolve this, we can make use of the +[CommandService.Log] event. + +All exceptions thrown during a command execution will be caught and +sent to the Log event under the [LogMessage.Exception] property as a +[CommandException] type. The [CommandException] class allows us to +access the exception thrown, as well as the context of the command. + +```cs +public async Task LogAsync(LogMessage logMessage) +{ + // This casting type requries C#7 + if (logMessage.Exception is CommandException cmdException) + { + // We can tell the user that something unexpected has happened + await cmdException.Context.Channel.SendMessageAsync("Something went catastrophically wrong!"); + + // We can also log this incident + Console.WriteLine($"{cmdException.Context.User} failed to execute '{cmdException.Command.Name}' in {cmdException.Context.Channel}."); + Console.WriteLine(cmdException.ToString()); + } +} +``` +[CommandException]: xref:Discord.Commands.CommandException +[LogMessage.Exception]: xref:Discord.Commands.LogMessage.Exception +[CommandService.Log]: xref:Discord.Commands.CommandService.Log +[RuntimeResult]: xref:Discord.Commands.RuntimeResult [CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted [ExecuteAsync]: xref:Discord.Commands.CommandService.ExecuteAsync* [ExecuteResult]: xref:Discord.Commands.ExecuteResult