| @@ -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 | |||