diff options
author | 2024-03-25 19:29:02 +0000 | |
---|---|---|
committer | 2024-03-25 19:29:02 +0000 | |
commit | 8f261610557f8b2ab8f47425159fe8b1efdd47ce (patch) | |
tree | b87877a2cb68308ab7679c9dd43db3165fdafd82 | |
parent | Bump pre-commit from 3.6.2 to 3.7.0 (#2969) (diff) |
Make help showable through button on command error message. (#2439)
* Make help showable through button on command error message.
* Improve error message
Improve error message for attempting to delete other users' command invocations
Co-authored-by: Boris Muratov <[email protected]>
* Use double quotes instead of single
* Refactor to use `ViewWithUserAndRoleCheck`
---------
Co-authored-by: Boris Muratov <[email protected]>
-rw-r--r-- | bot/exts/backend/error_handler.py | 59 | ||||
-rw-r--r-- | tests/bot/exts/backend/test_error_handler.py | 7 |
2 files changed, 51 insertions, 15 deletions
diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index faa39db5d..5cf07613d 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -1,10 +1,12 @@ import copy import difflib -from discord import Embed, Forbidden, Member +import discord +from discord import ButtonStyle, Embed, Forbidden, Interaction, Member, User from discord.ext.commands import ChannelNotFound, Cog, Context, TextChannelConverter, VoiceChannelConverter, errors from pydis_core.site_api import ResponseCodeError from pydis_core.utils.error_handling import handle_forbidden_from_block +from pydis_core.utils.interactions import DeleteMessageButton, ViewWithUserAndRoleCheck from sentry_sdk import push_scope from bot.bot import Bot @@ -16,6 +18,35 @@ from bot.utils.checks import ContextCheckFailure log = get_logger(__name__) +class HelpEmbedView(ViewWithUserAndRoleCheck): + """View to allow showing the help command for command error responses.""" + + def __init__(self, help_embed: Embed, owner: User | Member): + super().__init__(allowed_roles=MODERATION_ROLES, allowed_users=[owner.id]) + self.help_embed = help_embed + + self.delete_button = DeleteMessageButton() + self.add_item(self.delete_button) + + async def interaction_check(self, interaction: Interaction) -> bool: + """Overriden check to allow anyone to use the help button.""" + if (interaction.data or {}).get("custom_id") == self.help_button.custom_id: + log.trace( + "Allowed interaction by %s (%d) on %d as interaction was with the help button.", + interaction.user, + interaction.user.id, + interaction.message.id, + ) + return True + + return await super().interaction_check(interaction) + + @discord.ui.button(label="Help", style=ButtonStyle.primary) + async def help_button(self, interaction: Interaction, button: discord.ui.Button) -> None: + """Send an ephemeral message with the contents of the help command.""" + await interaction.response.send_message(embed=self.help_embed, ephemeral=True) + + class ErrorHandler(Cog): """Handles errors emitted from commands.""" @@ -117,15 +148,6 @@ class ErrorHandler(Cog): # ExtensionError await self.handle_unexpected_error(ctx, e) - async def send_command_help(self, ctx: Context) -> None: - """Return a prepared `help` command invocation coroutine.""" - if ctx.command: - self.bot.help_command.context = ctx - await ctx.send_help(ctx.command) - return - - await ctx.send_help() - async def try_silence(self, ctx: Context) -> bool: """ Attempt to invoke the silence or unsilence command if invoke with matches a pattern. @@ -300,8 +322,21 @@ class ErrorHandler(Cog): ) self.bot.stats.incr("errors.other_user_input_error") - await ctx.send(embed=embed) - await self.send_command_help(ctx) + await self.send_error_with_help(ctx, embed) + + async def send_error_with_help(self, ctx: Context, error_embed: Embed) -> None: + """Send error message, with button to show command help.""" + # Fall back to just sending the error embed if the custom help cog isn't loaded yet. + # ctx.command shouldn't be None here, but check just to be safe. + help_embed_creator = getattr(self.bot.help_command, "command_formatting", None) + if not help_embed_creator or not ctx.command: + await ctx.send(embed=error_embed) + return + + self.bot.help_command.context = ctx + help_embed, _ = await help_embed_creator(ctx.command) + view = HelpEmbedView(help_embed, ctx.author) + view.message = await ctx.send(embed=error_embed, view=view) @staticmethod async def handle_check_failure(ctx: Context, e: errors.CheckFailure) -> None: diff --git a/tests/bot/exts/backend/test_error_handler.py b/tests/bot/exts/backend/test_error_handler.py index 9670d42a0..dbc62270b 100644 --- a/tests/bot/exts/backend/test_error_handler.py +++ b/tests/bot/exts/backend/test_error_handler.py @@ -414,12 +414,13 @@ class IndividualErrorHandlerTests(unittest.IsolatedAsyncioTestCase): for case in test_cases: with self.subTest(error=case["error"], call_prepared=case["call_prepared"]): self.ctx.reset_mock() + self.cog.send_error_with_help = AsyncMock() self.assertIsNone(await self.cog.handle_user_input_error(self.ctx, case["error"])) - self.ctx.send.assert_awaited_once() if case["call_prepared"]: - self.ctx.send_help.assert_awaited_once() + self.cog.send_error_with_help.assert_awaited_once() else: - self.ctx.send_help.assert_not_awaited() + self.ctx.send.assert_awaited_once() + self.cog.send_error_with_help.assert_not_awaited() async def test_handle_check_failure_errors(self): """Should await `ctx.send` when error is check failure.""" |