diff --git a/docs/guides/commands/post-execution.md b/docs/guides/commands/post-execution.md index c741c7bde..2ebbabe3e 100644 --- a/docs/guides/commands/post-execution.md +++ b/docs/guides/commands/post-execution.md @@ -3,9 +3,6 @@ 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 @@ -40,49 +37,15 @@ may not always achieve the desired effect. 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. +sucessfully executed **without any runtime exceptions** or **without +any parsing or precondition failure**. 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); -} -``` +[!code[CommandExecuted demo](samples/command_executed_demo.cs)] So now we have a streamlined post-execution pipeline, great! What's next? We can take this further by using [RuntimeResult]. @@ -104,15 +67,31 @@ be returned when the command has finished its logic. 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. +class. The following creates a bare-minimum for a sub-class +of `RuntimeResult`, + +[!code[Base Use](samples/customresult_base.cs)] + +The sky's the limit from here. You can add any additional information +you'd like regarding the execution result. For example, you may +want to add your own result type or other helpful information +regarding the execution, or something simple like static methods to +help you create return types easily. + +[!code[Extended Use](samples/customresult_extended.cs)] + +After you're done creating your own [RuntimeResult], you can +implement it in your command by marking its return type to +`Task`. + +> ![NOTE] +> You must mark the return type as `Task` instead of +> `Task`. Only the former will be picked up when +> building the module. -```cs -public class MyCustomResult : RuntimeResult -{ -} -``` +Here's an example of a command that utilizes such logic: -// todo: finish this section +[!code[Usage](samples/customresult_usage.cs)] ## CommandService.Log Event @@ -126,21 +105,7 @@ 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()); - } -} -``` +[!code[Logger Sample](samples/command_exception_log.cs)] [CommandException]: xref:Discord.Commands.CommandException [LogMessage.Exception]: xref:Discord.LogMessage.Exception diff --git a/docs/guides/commands/samples/command_exception_log.cs b/docs/guides/commands/samples/command_exception_log.cs new file mode 100644 index 000000000..2956a0203 --- /dev/null +++ b/docs/guides/commands/samples/command_exception_log.cs @@ -0,0 +1,13 @@ +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()); + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/command_executed_demo.cs b/docs/guides/commands/samples/command_executed_demo.cs new file mode 100644 index 000000000..8d8fb911b --- /dev/null +++ b/docs/guides/commands/samples/command_executed_demo.cs @@ -0,0 +1,38 @@ +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) +{ + 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); + var result = await _commands.ExecuteAsync(context, argPos, _services); + // Optionally, you may pass the result manually into your + // CommandExecuted event handler if you wish to handle parsing or + // precondition failures in the same method. + + // await OnCommandExecutedAsync(null, context, result); +} \ No newline at end of file diff --git a/docs/guides/commands/samples/customresult_base.cs b/docs/guides/commands/samples/customresult_base.cs new file mode 100644 index 000000000..895a370c7 --- /dev/null +++ b/docs/guides/commands/samples/customresult_base.cs @@ -0,0 +1,6 @@ +public class MyCustomResult : RuntimeResult +{ + public MyCustomResult(CommandError? error, string reason) : base(error, reason) + { + } +} \ No newline at end of file diff --git a/docs/guides/commands/samples/customresult_extended.cs b/docs/guides/commands/samples/customresult_extended.cs new file mode 100644 index 000000000..6754c96aa --- /dev/null +++ b/docs/guides/commands/samples/customresult_extended.cs @@ -0,0 +1,10 @@ +public class MyCustomResult : RuntimeResult +{ + public MyCustomResult(CommandError? error, string reason) : base(error, reason) + { + } + public static MyCustomResult FromError(string reason) => + new MyCustomResult(CommandError.Unsuccessful, reason); + public static MyCustomResult FromSuccess(string reason = null) => + new MyCustomResult(null, reason); +} \ No newline at end of file diff --git a/docs/guides/commands/samples/customresult_usage.cs b/docs/guides/commands/samples/customresult_usage.cs new file mode 100644 index 000000000..f7ec303a2 --- /dev/null +++ b/docs/guides/commands/samples/customresult_usage.cs @@ -0,0 +1,10 @@ +public class MyModule : ModuleBase +{ + [Command("eat")] + public async Task ChooseAsync(string food) + { + if (food == "salad") + return MyCustomResult.FromError("No salad allowed!"); + return MyCustomResult.FromSuccess($"I'll take a {food}!"). + } +} \ No newline at end of file