| @@ -3,43 +3,149 @@ uid: Guides.Commands.PostExecution | |||||
| title: Post-command Execution Handling | title: Post-command Execution Handling | ||||
| --- | --- | ||||
| > [!WARNING] | |||||
| > This page is still under construction! | |||||
| # Preface | # Preface | ||||
| When developing a command system or modules, you may want to consider | 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. | 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, | example for executing and handling commands, | ||||
| [!code[Command Handler](samples/command_handler.cs)] | [!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 | result and print it to the chat. This is essentially the most | ||||
| basic post-execution handling. With this in mind, we could start doing | basic post-execution handling. With this in mind, we could start doing | ||||
| things like the following, | things like the following, | ||||
| [!code[Basic Command Handler](samples/post-execution_basic.cs)] | [!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`. | 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. | may not always achieve the desired effect. | ||||
| ## CommandExecuted Event | ## 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 | [CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted | ||||
| [ExecuteAsync]: xref:Discord.Commands.CommandService.ExecuteAsync* | [ExecuteAsync]: xref:Discord.Commands.CommandService.ExecuteAsync* | ||||
| [ExecuteResult]: xref:Discord.Commands.ExecuteResult | [ExecuteResult]: xref:Discord.Commands.ExecuteResult | ||||