Пример #1
0
    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
Пример #2
0
    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
Пример #3
0
    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)
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
    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)
Пример #7
0
    '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())