Skip to content

Commit

Permalink
Correct error propagation for help command
Browse files Browse the repository at this point in the history
  • Loading branch information
trumully committed Sep 24, 2024
1 parent de2fa21 commit 767e38b
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 40 deletions.
2 changes: 1 addition & 1 deletion dynamo/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
P = ParamSpec("P")
T = TypeVar("T")

AsyncCallable = TypeVar("AC", bound=Callable[P, Coroutine[Any, Any, T]])
AsyncCallable = TypeVar("AsyncCallable", bound=Callable[P, Coroutine[Any, Any, T]])
CogT = TypeVar("CogT", bound=commands.Cog)
CommandT = TypeVar("CommandT", bound=commands.Command[CogT, P, T])
ContextT = TypeVar("ContextT", bound=commands.Context[Any], covariant=True)
53 changes: 18 additions & 35 deletions dynamo/extensions/cogs/errors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Callable, Coroutine, Mapping
from typing import Any, Callable, Coroutine

import discord
from discord import Interaction, app_commands
Expand All @@ -7,41 +7,21 @@

from dynamo.core import Dynamo, DynamoCog
from dynamo.utils.context import Context
from dynamo.utils.error_types import NotFoundWithHelp, app_command_error_messages, command_error_messages


class Errors(DynamoCog):
"""Handles errors for the bot."""

command_error_messages: Mapping[type[commands.CommandError], str] = {
commands.CommandNotFound: "Command not found: **`{}`**{}",
commands.MissingRequiredArgument: "Missing required argument: `{}`.",
commands.BadArgument: "Bad argument.",
commands.CommandOnCooldown: "You are on cooldown. Try again in `{:.2f}` seconds.",
commands.TooManyArguments: "Too many arguments.",
commands.MissingPermissions: "You are not allowed to use this command.",
commands.BotMissingPermissions: "I am not allowed to use this command.",
commands.NoPrivateMessage: "This command can only be used in a server.",
commands.NotOwner: "You are not the owner of this bot.",
commands.DisabledCommand: "This command is disabled.",
commands.CheckFailure: "You do not have permission to use this command.",
}

app_command_error_messages: Mapping[type[app_commands.AppCommandError], str] = {
app_commands.CommandNotFound: "Command not found: **`{}`**{}",
app_commands.CommandOnCooldown: "You are on cooldown. Try again in `{:.2f}` seconds.",
app_commands.MissingPermissions: "You are not allowed to use this command.",
app_commands.BotMissingPermissions: "I am not allowed to use this command.",
app_commands.NoPrivateMessage: "This command can only be used in a server.",
app_commands.CheckFailure: "You do not have permission to use this command.",
}

def __init__(self, bot: Dynamo) -> None:
super().__init__(bot)

self.old_tree_error: Callable[
[Interaction[Dynamo], app_commands.AppCommandError], Coroutine[Any, Any, None]
] = self.bot.tree.on_error
self.bot.tree.on_error = self.on_app_command_error
self.command_error_messages = command_error_messages
self.app_command_error_messages = app_command_error_messages

async def cog_unload(self) -> None:
"""Restores the old tree error handler on unload."""
Expand Down Expand Up @@ -93,14 +73,19 @@ async def on_command_error(self, ctx: Context, error: commands.CommandError) ->

error_message = self.get_command_error_message(error)

if isinstance(error, commands.CommandNotFound):
if isinstance(error, (commands.CommandNotFound, NotFoundWithHelp)):
trigger = ctx.invoked_with if isinstance(error, commands.CommandNotFound) else error.args[0]

matches = [
f"**{command.qualified_name}** - {command.short_doc or 'No description provided'}"
for command in self.bot.commands
if fuzz.partial_ratio(ctx.invoked_with, command.name) > 70
if fuzz.ratio(trigger, command.name) > 70
]
matches_string = f"\n\nDid you mean {f'\N{RIGHT-POINTING MAGNIFYING GLASS}\n>>> {'\n'.join(matches)}' if matches else ''}"
error_message = error_message.format(ctx.invoked_with, matches_string)
matches_string = (
f"\n\nDid you mean \N{RIGHT-POINTING MAGNIFYING GLASS}\n>>> {'\n'.join(matches)}" if matches else ""
)

error_message = error_message.format(trigger, matches_string)

elif isinstance(error, commands.MissingRequiredArgument):
error_message = error_message.format(error.param.name)
Expand All @@ -126,14 +111,12 @@ async def on_app_command_error(self, interaction: Interaction, error: app_comman
self.log.error("Command not found: %s.", interaction.data)
command_name = interaction.data.get("name", "")
matches = [
command
for command in self.bot.tree.get_commands()
if fuzz.partial_ratio(command_name, command.name) > 70
command for command in self.bot.tree.get_commands() if fuzz.ratio(command_name, command.name) > 70
]
msg = (
f"Command not found: '{command_name}'"
f"\n\n{f'Did you mean \N{RIGHT-POINTING MAGNIFYING GLASS}\n>>> {'\n'.join(matches)}' if matches else ''}"
)
msg = f"Command not found: '{command_name}'"
if matches:
msg += f"\n\nDid you mean \N{RIGHT-POINTING MAGNIFYING GLASS}\n>>> {'\n'.join(matches)}"

await interaction.response.send_message(
ephemeral=True,
content=msg,
Expand Down
13 changes: 9 additions & 4 deletions dynamo/extensions/cogs/help.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import logging
from dataclasses import dataclass
from typing import Any, Mapping, TypedDict
from typing import Any, Mapping, Never, TypedDict

import discord
from discord.ext import commands

from dynamo._typing import CogT, CommandT
from dynamo.core import Dynamo, DynamoCog
from dynamo.utils.error_types import NotFoundWithHelp
from dynamo.utils.format import code_block, human_join

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -49,15 +50,19 @@ async def send_bot_help(self, mapping: Mapping[CogT, list[CommandT]]) -> None:
embed = HelpEmbed(title=f"{ctx.me.display_name} Help")
embed.set_thumbnail(url=ctx.me.display_avatar)

for cog, commands in mapping.items():
filtered_commands = await self.filter_commands(commands)
for cog, command in mapping.items():
filtered_commands = await self.filter_commands(command)
if (cog and cog.qualified_name not in self.blacklisted) and filtered_commands:
cog_field = await self.add_cog_commands_to_embed(cog, commands)
cog_field = await self.add_cog_commands_to_embed(cog, command)
if cog_field is not None:
embed.add_field(**cog_field)

await self.send(embed=embed)

def command_not_found(self, string: str) -> Never:
log.debug("Command not found: %s", string)
raise NotFoundWithHelp(string)

async def add_cog_commands_to_embed(self, cog: CogT, commands: list[CommandT]) -> EmbedField | None:
name = cog.qualified_name if cog else "None"
filtered_commands = await self.filter_commands(commands)
Expand Down
32 changes: 32 additions & 0 deletions dynamo/utils/error_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from collections.abc import Mapping

from discord import app_commands
from discord.ext import commands


class NotFoundWithHelp(commands.CommandError): ...


command_error_messages: Mapping[type[commands.CommandError], str] = {
commands.CommandNotFound: "Command not found: **`{}`**{}",
NotFoundWithHelp: "Command not found: **`{}`**{}",
commands.MissingRequiredArgument: "Missing required argument: `{}`.",
commands.BadArgument: "Bad argument.",
commands.CommandOnCooldown: "You are on cooldown. Try again in `{:.2f}` seconds.",
commands.TooManyArguments: "Too many arguments.",
commands.MissingPermissions: "You are not allowed to use this command.",
commands.BotMissingPermissions: "I am not allowed to use this command.",
commands.NoPrivateMessage: "This command can only be used in a server.",
commands.NotOwner: "You are not the owner of this bot.",
commands.DisabledCommand: "This command is disabled.",
commands.CheckFailure: "You do not have permission to use this command.",
}

app_command_error_messages: Mapping[type[app_commands.AppCommandError], str] = {
app_commands.CommandNotFound: "Command not found: **`{}`**{}",
app_commands.CommandOnCooldown: "You are on cooldown. Try again in `{:.2f}` seconds.",
app_commands.MissingPermissions: "You are not allowed to use this command.",
app_commands.BotMissingPermissions: "I am not allowed to use this command.",
app_commands.NoPrivateMessage: "This command can only be used in a server.",
app_commands.CheckFailure: "You do not have permission to use this command.",
}

0 comments on commit 767e38b

Please sign in to comment.