Skip to content

Make help showable through button on command error message. #2439

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 25, 2024
Merged
59 changes: 47 additions & 12 deletions bot/exts/backend/error_handler.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,6 +18,35 @@
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."""

Expand Down Expand Up @@ -117,15 +148,6 @@ async def on_command_error(self, ctx: Context, e: errors.CommandError) -> None:
# 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.
Expand Down Expand Up @@ -300,8 +322,21 @@ async def handle_user_input_error(self, ctx: Context, e: errors.UserInputError)
)
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:
Expand Down
7 changes: 4 additions & 3 deletions tests/bot/exts/backend/test_error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,13 @@ async def test_handle_input_error_handler_errors(self):
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."""
Expand Down