From 53a5b3234d085e6527fdb5df75b3ef94ac1c3879 Mon Sep 17 00:00:00 2001 From: Richard Si <63936253+ichard26@users.noreply.github.com> Date: Sat, 25 Jun 2022 21:53:43 -0400 Subject: Help: remove redundant space in subcommand aliases `parent` already has a trailing space so let's not add *another* one. --- bot/exts/core/help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/core/help.py') diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index db3c2aa6..f9b3513f 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -306,7 +306,7 @@ class HelpSession: signature = self._get_command_params(self.query) parent = self.query.full_parent_name + " " if self.query.parent else "" paginator.add_line(f"**```\n{prefix}{parent}{signature}\n```**") - aliases = [f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in self.query.aliases] + aliases = [f"`{alias}`" if not parent else f"`{parent}{alias}`" for alias in self.query.aliases] aliases += [f"`{alias}`" for alias in getattr(self.query, "root_aliases", ())] aliases = ", ".join(sorted(aliases)) if aliases: -- cgit v1.2.3 From 4e9aa1ec0a4ee3a8c281d78d766c78796a741863 Mon Sep 17 00:00:00 2001 From: Rohan Reddy Alleti Date: Thu, 18 Aug 2022 21:41:47 +0530 Subject: Help command fix, normalize suggestions for unknown commands (#1064) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/core/error_handler.py | 43 +++++++++++++++++++++--------------------- bot/exts/core/help.py | 40 +++++++++++++++++++++++++++------------ bot/utils/commands.py | 11 +++++++++++ 3 files changed, 61 insertions(+), 33 deletions(-) create mode 100644 bot/utils/commands.py (limited to 'bot/exts/core/help.py') diff --git a/bot/exts/core/error_handler.py b/bot/exts/core/error_handler.py index 983632ba..4578f734 100644 --- a/bot/exts/core/error_handler.py +++ b/bot/exts/core/error_handler.py @@ -1,4 +1,3 @@ -import difflib import logging import math import random @@ -11,6 +10,7 @@ from sentry_sdk import push_scope from bot.bot import Bot from bot.constants import Channels, Colours, ERROR_REPLIES, NEGATIVE_REPLIES, RedirectOutput +from bot.utils.commands import get_command_suggestions from bot.utils.decorators import InChannelCheckFailure, InMonthCheckFailure from bot.utils.exceptions import APIError, MovedCommandError, UserNotPlayingError @@ -158,31 +158,32 @@ class CommandErrorHandler(commands.Cog): async def send_command_suggestion(self, ctx: commands.Context, command_name: str) -> None: """Sends user similar commands if any can be found.""" - raw_commands = [] - for cmd in self.bot.walk_commands(): - if not cmd.hidden: - raw_commands += (cmd.name, *cmd.aliases) - if similar_command_data := difflib.get_close_matches(command_name, raw_commands, 1): - similar_command_name = similar_command_data[0] - similar_command = self.bot.get_command(similar_command_name) - - if not similar_command: - return - - log_msg = "Cancelling attempt to suggest a command due to failed checks." - try: - if not await similar_command.can_run(ctx): + command_suggestions = [] + if similar_command_names := get_command_suggestions(list(self.bot.all_commands.keys()), command_name): + for similar_command_name in similar_command_names: + similar_command = self.bot.get_command(similar_command_name) + + if not similar_command: + continue + + log_msg = "Cancelling attempt to suggest a command due to failed checks." + try: + if not await similar_command.can_run(ctx): + log.debug(log_msg) + continue + except commands.errors.CommandError as cmd_error: log.debug(log_msg) - return - except commands.errors.CommandError as cmd_error: - log.debug(log_msg) - await self.on_command_error(ctx, cmd_error) - return + await self.on_command_error(ctx, cmd_error) + continue + + command_suggestions.append(similar_command_name) misspelled_content = ctx.message.content e = Embed() e.set_author(name="Did you mean:", icon_url=QUESTION_MARK_ICON) - e.description = misspelled_content.replace(command_name, similar_command_name, 1) + e.description = "\n".join( + misspelled_content.replace(command_name, cmd, 1) for cmd in command_suggestions + ) await ctx.send(embed=e, delete_after=RedirectOutput.delete_delay) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index f9b3513f..b5df70ca 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -3,16 +3,16 @@ import asyncio import itertools import logging from contextlib import suppress -from typing import NamedTuple, Union +from typing import NamedTuple, Optional, Union from discord import Colour, Embed, HTTPException, Message, Reaction, User from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context -from rapidfuzz import process from bot import constants from bot.bot import Bot from bot.constants import Emojis +from bot.utils.commands import get_command_suggestions from bot.utils.pagination import FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI DELETE_EMOJI = Emojis.trashcan @@ -41,14 +41,18 @@ class HelpQueryNotFound(ValueError): """ Raised when a HelpSession Query doesn't match a command or cog. - Contains the custom attribute of ``possible_matches``. - Instances of this object contain a dictionary of any command(s) that were close to matching the - query, where keys are the possible matched command names and values are the likeness match scores. + Params: + possible_matches: list of similar command names. + parent_command: parent command of an invalid subcommand. Only available when an invalid subcommand + has been passed. """ - def __init__(self, arg: str, possible_matches: dict = None): + def __init__( + self, arg: str, possible_matches: Optional[list[str]] = None, *, parent_command: Optional[Command] = None + ) -> None: super().__init__(arg) self.possible_matches = possible_matches + self.parent_command = parent_command class HelpSession: @@ -153,12 +157,17 @@ class HelpSession: Will pass on possible close matches along with the `HelpQueryNotFound` exception. """ - # Combine command and cog names - choices = list(self._bot.all_commands) + list(self._bot.cogs) + # Check if parent command is valid in case subcommand is invalid. + if " " in query: + parent, *_ = query.split() + parent_command = self._bot.get_command(parent) + + if parent_command: + raise HelpQueryNotFound('Invalid Subcommand.', parent_command=parent_command) - result = process.extract(query, choices, score_cutoff=90) + similar_commands = get_command_suggestions(list(self._bot.all_commands.keys()), query) - raise HelpQueryNotFound(f'Query "{query}" not found.', dict(result)) + raise HelpQueryNotFound(f'Query "{query}" not found.', similar_commands) async def timeout(self, seconds: int = 30) -> None: """Waits for a set number of seconds, then stops the help session.""" @@ -507,13 +516,20 @@ class Help(DiscordCog): try: await HelpSession.start(ctx, *commands) except HelpQueryNotFound as error: + + # Send help message of parent command if subcommand is invalid. + if cmd := error.parent_command: + await ctx.send(str(error)) + await self.new_help(ctx, cmd.qualified_name) + return + embed = Embed() embed.colour = Colour.red() embed.title = str(error) if error.possible_matches: - matches = "\n".join(error.possible_matches.keys()) - embed.description = f"**Did you mean:**\n`{matches}`" + matches = "\n".join(error.possible_matches) + embed.description = f"**Did you mean:**\n{matches}" await ctx.send(embed=embed) diff --git a/bot/utils/commands.py b/bot/utils/commands.py new file mode 100644 index 00000000..7c04a25a --- /dev/null +++ b/bot/utils/commands.py @@ -0,0 +1,11 @@ +from typing import Optional + +from rapidfuzz import process + + +def get_command_suggestions( + all_commands: list[str], query: str, *, cutoff: int = 60, limit: int = 3 +) -> Optional[list]: + """Get similar command names.""" + results = process.extract(query, all_commands, score_cutoff=cutoff, limit=limit) + return [result[0] for result in results] -- cgit v1.2.3 From c4b45fe512f71776f8d4ef6698b88ed8bc4606c9 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 10 Jul 2022 12:50:41 +0100 Subject: Update help command to work with root_aliases Specifying root_aliases causes bookmark delete to be registered as a command rather than subcommand, which causes it to be listed even though subcommands usually aren't, and this isn't done correctly. Co-authored-by: wookie184 --- bot/exts/core/help.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'bot/exts/core/help.py') diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index b5df70ca..7d8066df 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -286,7 +286,7 @@ class HelpSession: else: results.append(f"<{name}>") - return f"{cmd.name} {' '.join(results)}" + return f"{cmd.qualified_name} {' '.join(results)}" async def build_pages(self) -> None: """Builds the list of content pages to be paginated through in the help message, as a list of str.""" @@ -313,6 +313,8 @@ class HelpSession: prefix = constants.Client.prefix signature = self._get_command_params(self.query) + paginator.add_line(f"**```\n{prefix}{signature}\n```**") + parent = self.query.full_parent_name + " " if self.query.parent else "" paginator.add_line(f"**```\n{prefix}{parent}{signature}\n```**") aliases = [f"`{alias}`" if not parent else f"`{parent}{alias}`" for alias in self.query.aliases] -- cgit v1.2.3 From aa2cdf594892b563f3d8ba24aa4f04d43a85e331 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 18 Aug 2022 18:34:51 +0100 Subject: Don't output command name twice in help command --- bot/exts/core/help.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot/exts/core/help.py') diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 7d8066df..7384c27d 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -316,7 +316,6 @@ class HelpSession: paginator.add_line(f"**```\n{prefix}{signature}\n```**") parent = self.query.full_parent_name + " " if self.query.parent else "" - paginator.add_line(f"**```\n{prefix}{parent}{signature}\n```**") aliases = [f"`{alias}`" if not parent else f"`{parent}{alias}`" for alias in self.query.aliases] aliases += [f"`{alias}`" for alias in getattr(self.query, "root_aliases", ())] aliases = ", ".join(sorted(aliases)) -- cgit v1.2.3 From 702144d1ac88696cf045f12e1972cfe6f58335e5 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 18 Aug 2022 18:35:25 +0100 Subject: Allow help in DMs Since there are some comands that work in DMs, we should allow the command to be ran there. --- bot/exts/core/help.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot/exts/core/help.py') diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 7384c27d..eb7a9762 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -13,6 +13,7 @@ from bot import constants from bot.bot import Bot from bot.constants import Emojis from bot.utils.commands import get_command_suggestions +from bot.utils.decorators import whitelist_override from bot.utils.pagination import FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI DELETE_EMOJI = Emojis.trashcan @@ -512,6 +513,7 @@ class Help(DiscordCog): """Custom Embed Pagination Help feature.""" @commands.command("help") + @whitelist_override(allow_dm=True) async def new_help(self, ctx: Context, *commands) -> None: """Shows Command Help.""" try: -- cgit v1.2.3