diff options
| -rw-r--r-- | Pipfile.lock | 8 | ||||
| -rw-r--r-- | bot/exts/backend/error_handler.py | 40 | ||||
| -rw-r--r-- | bot/exts/info/tags.py | 24 |
3 files changed, 63 insertions, 9 deletions
diff --git a/Pipfile.lock b/Pipfile.lock index fbae5b3db..3f9c32d62 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -580,7 +580,7 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.4.7" }, "pyreadline": { @@ -655,7 +655,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.15.0" }, "snowballstemmer": { @@ -1097,7 +1097,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.15.0" }, "snowballstemmer": { @@ -1112,7 +1112,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "urllib3": { diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index 5b5840858..2402fa175 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -1,4 +1,5 @@ import contextlib +import difflib import logging import typing as t @@ -8,7 +9,7 @@ from sentry_sdk import push_scope from bot.api import ResponseCodeError from bot.bot import Bot -from bot.constants import Colours +from bot.constants import Channels, Colours, Icons, MODERATION_ROLES from bot.converters import TagNameConverter from bot.errors import LockedResourceError from bot.utils.checks import InWhitelistCheckFailure @@ -155,9 +156,46 @@ class ErrorHandler(Cog): else: with contextlib.suppress(ResponseCodeError): await ctx.invoke(tags_get_command, tag_name=tag_name) + + if not any(role.id in MODERATION_ROLES for role in ctx.author.roles): + tags_cog = self.bot.get_cog("Tags") + command_name = ctx.invoked_with + sent = await tags_cog.display_tag(ctx, command_name) + + if not sent: + await self.send_command_suggestion(ctx, command_name) + # Return to not raise the exception return + async def send_command_suggestion(self, ctx: Context, command_name: str) -> None: + """Sends user similar commands if any can be found.""" + # No similar tag found, or tag on cooldown - + # searching for a similar command + 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) + + 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) + return + except errors.CommandError as cmd_error: + log.debug(log_msg) + await self.on_command_error(ctx, cmd_error) + return + + misspelled_content = ctx.message.content + e = Embed() + e.set_author(name="Did you mean:", icon_url=Icons.questionmark) + e.description = f"{misspelled_content.replace(command_name, similar_command_name, 1)}" + await ctx.send(embed=e, delete_after=10.0) + async def handle_user_input_error(self, ctx: Context, e: errors.UserInputError) -> None: """ Send an error message in `ctx` for UserInputError, sometimes invoking the help command too. diff --git a/bot/exts/info/tags.py b/bot/exts/info/tags.py index da4154316..639286d90 100644 --- a/bot/exts/info/tags.py +++ b/bot/exts/info/tags.py @@ -182,10 +182,15 @@ class Tags(Cog): matching_tags = self._get_tags_via_content(any, keywords or 'any', ctx.author) await self._send_matching_tags(ctx, keywords, matching_tags) - @tags_group.command(name='get', aliases=('show', 'g')) - async def get_command(self, ctx: Context, *, tag_name: TagNameConverter = None) -> None: - """Get a specified tag, or a list of all tags if no tag is specified.""" + async def display_tag(self, ctx: Context, tag_name: str = None) -> bool: + """ + If a tag is not found, display similar tag names as suggestions. + If a tag is not specified, display a paginated embed of all tags. + + Tags are on cooldowns on a per-tag, per-channel basis. If a tag is on cooldown, display + nothing and return False. + """ def _command_on_cooldown(tag_name: str) -> bool: """ Check if the command is currently on cooldown, on a per-tag, per-channel basis. @@ -212,7 +217,7 @@ class Tags(Cog): f"{ctx.author} tried to get the '{tag_name}' tag, but the tag is on cooldown. " f"Cooldown ends in {time_left:.1f} seconds." ) - return + return False if tag_name is not None: temp_founds = self._get_tag(tag_name) @@ -237,6 +242,7 @@ class Tags(Cog): await ctx.send(embed=Embed.from_dict(tag['embed'])), [ctx.author.id], ) + return True elif founds and len(tag_name) >= 3: await wait_for_deletion( await ctx.send( @@ -247,6 +253,7 @@ class Tags(Cog): ), [ctx.author.id], ) + return True else: tags = self._cache.values() @@ -255,6 +262,7 @@ class Tags(Cog): description="**There are no tags in the database!**", colour=Colour.red() )) + return True else: embed: Embed = Embed(title="**Current tags**") await LinePaginator.paginate( @@ -268,6 +276,14 @@ class Tags(Cog): empty=False, max_lines=15 ) + return True + + return False + + @tags_group.command(name='get', aliases=('show', 'g')) + async def get_command(self, ctx: Context, *, tag_name: TagNameConverter = None) -> None: + """Get a specified tag, or a list of all tags if no tag is specified.""" + await self.display_tag(ctx, tag_name) def setup(bot: Bot) -> None: |