async def try_silence(self, ctx: Context) -> bool: """ Attempt to invoke the silence or unsilence command if invoke with matches a pattern. Respecting the checks if: * invoked with `shh+` silence channel for amount of h's*2 with max of 15. * invoked with `unshh+` unsilence channel Return bool depending on success of command. """ command = ctx.invoked_with.lower() silence_command = self.bot.get_command("silence") ctx.invoked_from_error_handler = True try: if not await silence_command.can_run(ctx): log.debug( "Cancelling attempt to invoke silence/unsilence due to failed checks." ) return False except errors.CommandError: log.debug( "Cancelling attempt to invoke silence/unsilence due to failed checks." ) return False if command.startswith("shh"): await ctx.invoke(silence_command, duration=min(command.count("h") * 2, 15)) return True elif command.startswith("unshh"): await ctx.invoke(self.bot.get_command("unsilence")) return True return False
async def try_get_tag(self, ctx: Context) -> None: """ Attempt to display a tag by interpreting the command name as a tag name. The invocation of tags get respects its checks. Any CommandErrors raised will be handled by `on_command_error`, but the `invoked_from_error_handler` attribute will be added to the context to prevent infinite recursion in the case of a CommandNotFound exception. """ tags_get_command = self.bot.get_command("tags get") ctx.invoked_from_error_handler = True log_msg = "Cancelling attempt to fall back to a tag due to failed checks." try: if not await tags_get_command.can_run(ctx): log.debug(log_msg) return except errors.CommandError as tag_error: log.debug(log_msg) await self.on_command_error(ctx, tag_error) return try: tag_name = await TagNameConverter.convert(ctx, ctx.invoked_with) except errors.BadArgument: log.debug( f"{ctx.author} tried to use an invalid command " f"and the fallback tag failed validation in TagNameConverter.") else: with contextlib.suppress(ResponseCodeError): await ctx.invoke(tags_get_command, tag_name=tag_name) # Return to not raise the exception return
async def try_get_tag(self, ctx: Context) -> None: """ Attempt to display a tag by interpreting the command name as a tag name. The invocation of tags get respects its checks. Any CommandErrors raised will be handled by `on_command_error`, but the `invoked_from_error_handler` attribute will be added to the context to prevent infinite recursion in the case of a CommandNotFound exception. """ tags_get_command = self.bot.get_command("tags get") ctx.invoked_from_error_handler = True log_msg = "Cancelling attempt to fall back to a tag due to failed checks." try: if not await tags_get_command.can_run(ctx): log.debug(log_msg) return except errors.CommandError as tag_error: log.debug(log_msg) await self.on_command_error(ctx, tag_error) return if await ctx.invoke(tags_get_command, argument_string=ctx.message.content): return if not any(role.id in MODERATION_ROLES for role in ctx.author.roles): await self.send_command_suggestion(ctx, ctx.invoked_with)
async def try_silence(self, ctx: Context) -> bool: """ Attempt to invoke the silence or unsilence command if invoke with matches a pattern. Respecting the checks if: * invoked with `shh+` silence channel for amount of h's*2 with max of 15. * invoked with `unshh+` unsilence channel Return bool depending on success of command. """ command = ctx.invoked_with.lower() args = ctx.message.content.lower().split(" ") silence_command = self.bot.get_command("silence") ctx.invoked_from_error_handler = True try: if not await silence_command.can_run(ctx): log.debug( "Cancelling attempt to invoke silence/unsilence due to failed checks." ) return False except errors.CommandError: log.debug( "Cancelling attempt to invoke silence/unsilence due to failed checks." ) return False # Parse optional args channel = None duration = min(command.count("h") * 2, 15) kick = False if len(args) > 1: # Parse channel for converter in (TextChannelConverter(), VoiceChannelConverter()): try: channel = await converter.convert(ctx, args[1]) break except ChannelNotFound: continue if len(args) > 2 and channel is not None: # Parse kick kick = args[2].lower() == "true" if command.startswith("shh"): await ctx.invoke(silence_command, duration_or_channel=channel, duration=duration, kick=kick) return True elif command.startswith("unshh"): await ctx.invoke(self.bot.get_command("unsilence"), channel=channel) return True return False
async def on_command_error(self, ctx: Context, e: CommandError): command = ctx.command parent = None if command is not None: parent = command.parent if parent and command: help_command = (self.bot.get_command("help"), parent.name, command.name) elif command: help_command = (self.bot.get_command("help"), command.name) else: help_command = (self.bot.get_command("help"), ) if hasattr(command, "on_error"): log.debug( f"Command {command} has a local error handler, ignoring.") return if isinstance(e, CommandNotFound) and not hasattr( ctx, "invoked_from_error_handler"): tags_get_command = self.bot.get_command("tags get") ctx.invoked_from_error_handler = True # Return to not raise the exception return await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with) elif isinstance(e, BadArgument): await ctx.send(f"Bad argument: {e}\n") await ctx.invoke(*help_command) elif isinstance(e, UserInputError): await ctx.invoke(*help_command) elif isinstance(e, NoPrivateMessage): await ctx.send( "Sorry, this command can't be used in a private message!") elif isinstance(e, BotMissingPermissions): await ctx.send( f"Sorry, it looks like I don't have the permissions I need to do that.\n\n" f"Here's what I'm missing: **{e.missing_perms}**") elif isinstance(e, CommandInvokeError): await ctx.send( f"Sorry, an unexpected error occurred. Please let us know!\n\n```{e}```" ) raise e.original raise e
async def on_command_error(self, ctx: Context, e: CommandError) -> None: """ Provide generic command error handling. Error handling is deferred to any local error handler, if present. Error handling emits a single error response, prioritized as follows: 1. If the name fails to match a command but matches a tag, the tag is invoked 2. Send a BadArgument error message to the invoking context & invoke the command's help 3. Send a UserInputError error message to the invoking context & invoke the command's help 4. Send a NoPrivateMessage error message to the invoking context 5. Send a BotMissingPermissions error message to the invoking context 6. Log a MissingPermissions error, no message is sent 7. Send a InChannelCheckFailure error message to the invoking context 8. Log CheckFailure, CommandOnCooldown, and DisabledCommand errors, no message is sent 9. For CommandInvokeErrors, response is based on the type of error: * 404: Error message is sent to the invoking context * 400: Log the resopnse JSON, no message is sent * 500 <= status <= 600: Error message is sent to the invoking context 10. Otherwise, handling is deferred to `handle_unexpected_error` """ command = ctx.command parent = None if command is not None: parent = command.parent # Retrieve the help command for the invoked command. if parent and command: help_command = (self.bot.get_command("help"), parent.name, command.name) elif command: help_command = (self.bot.get_command("help"), command.name) else: help_command = (self.bot.get_command("help"),) if hasattr(e, "handled"): log.trace(f"Command {command} had its error already handled locally; ignoring.") return # Try to look for a tag with the command's name if the command isn't found. if isinstance(e, CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"): if not ctx.channel.id == Channels.verification: tags_get_command = self.bot.get_command("tags get") ctx.invoked_from_error_handler = True log_msg = "Cancelling attempt to fall back to a tag due to failed checks." try: if not await tags_get_command.can_run(ctx): log.debug(log_msg) return except CommandError as tag_error: log.debug(log_msg) await self.on_command_error(ctx, tag_error) return # Return to not raise the exception with contextlib.suppress(ResponseCodeError): await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with) return elif isinstance(e, BadArgument): await ctx.send(f"Bad argument: {e}\n") await ctx.invoke(*help_command) elif isinstance(e, UserInputError): await ctx.send("Something about your input seems off. Check the arguments:") await ctx.invoke(*help_command) log.debug( f"Command {command} invoked by {ctx.message.author} with error " f"{e.__class__.__name__}: {e}" ) elif isinstance(e, NoPrivateMessage): await ctx.send("Sorry, this command can't be used in a private message!") elif isinstance(e, BotMissingPermissions): await ctx.send(f"Sorry, it looks like I don't have the permissions I need to do that.") log.warning( f"The bot is missing permissions to execute command {command}: {e.missing_perms}" ) elif isinstance(e, MissingPermissions): log.debug( f"{ctx.message.author} is missing permissions to invoke command {command}: " f"{e.missing_perms}" ) elif isinstance(e, InChannelCheckFailure): await ctx.send(e) elif isinstance(e, (CheckFailure, CommandOnCooldown, DisabledCommand)): log.debug( f"Command {command} invoked by {ctx.message.author} with error " f"{e.__class__.__name__}: {e}" ) elif isinstance(e, CommandInvokeError): if isinstance(e.original, ResponseCodeError): status = e.original.response.status if status == 404: await ctx.send("There does not seem to be anything matching your query.") elif status == 400: content = await e.original.response.json() log.debug(f"API responded with 400 for command {command}: %r.", content) await ctx.send("According to the API, your request is malformed.") elif 500 <= status < 600: await ctx.send("Sorry, there seems to be an internal issue with the API.") log.warning(f"API responded with {status} for command {command}") else: await ctx.send(f"Got an unexpected status code from the API (`{status}`).") log.warning(f"Unexpected API response for command {command}: {status}") else: await self.handle_unexpected_error(ctx, e.original) else: await self.handle_unexpected_error(ctx, e)
'tts': None, 'content': 'content', 'nonce': None, } state = unittest.mock.MagicMock() channel = unittest.mock.MagicMock() message_instance = discord.Message(state=state, channel=channel, data=message_data) # Create a Context instance to get a realistic MagicMock of `discord.ext.commands.Context` context_instance = Context(message=unittest.mock.MagicMock(), prefix="$", bot=MockBot(), view=None) context_instance.invoked_from_error_handler = None class MockContext(CustomMockMixin, unittest.mock.MagicMock): """ A MagicMock subclass to mock Context objects. Instances of this class will follow the specifications of `discord.ext.commands.Context` instances. For more information, see the `MockGuild` docstring. """ spec_set = context_instance def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.me = kwargs.get('me', MockMember()) self.bot = kwargs.get('bot', MockBot())