diff options
| -rw-r--r-- | bot/cogs/information.py | 35 | ||||
| -rw-r--r-- | bot/cogs/tags.py | 2 | ||||
| -rw-r--r-- | bot/decorators.py | 45 | ||||
| -rw-r--r-- | bot/utils/checks.py | 56 | 
4 files changed, 112 insertions, 26 deletions
diff --git a/bot/cogs/information.py b/bot/cogs/information.py index 7a244cdbe..12970bd0e 100644 --- a/bot/cogs/information.py +++ b/bot/cogs/information.py @@ -1,11 +1,13 @@  import logging +import random  import textwrap  from discord import CategoryChannel, Colour, Embed, Member, TextChannel, VoiceChannel -from discord.ext.commands import Bot, Context, command +from discord.ext.commands import BadArgument, Bot, CommandError, Context, MissingPermissions, command -from bot.constants import Emojis, Keys, Roles, URLs +from bot.constants import Channels, Emojis, Keys, NEGATIVE_REPLIES, Roles, URLs  from bot.decorators import with_role +from bot.utils.checks import with_role_check  from bot.utils.time import time_since  log = logging.getLogger(__name__) @@ -121,13 +123,23 @@ class Information:          await ctx.send(embed=embed) -    @with_role(*MODERATION_ROLES)      @command(name="user", aliases=["user_info", "member", "member_info"])      async def user_info(self, ctx: Context, user: Member = None, hidden: bool = False):          """          Returns info about a user.          """ +        # Do a role check if this is being executed on +        # someone other than the caller +        if user and user != ctx.author: +            if not with_role_check(ctx, *MODERATION_ROLES): +                raise BadArgument("Only mods can target other users.") + +        # Non-moderators may only do this in #bot-commands +        if not with_role_check(ctx, *MODERATION_ROLES): +            if not ctx.channel.id == Channels.bot: +                raise MissingPermissions("You can't do that here!") +          # Validates hidden input          hidden = str(hidden) @@ -192,6 +204,23 @@ class Information:          await ctx.send(embed=embed) +    @user_info.error +    async def user_info_command_error(self, ctx: Context, error: CommandError): +        embed = Embed(colour=Colour.red()) + +        if isinstance(error, BadArgument): +            embed.title = random.choice(NEGATIVE_REPLIES) +            embed.description = "You do not have permission to use this command on users other than yourself." +            await ctx.send(embed=embed) + +        elif isinstance(error, MissingPermissions): +            embed.title = random.choice(NEGATIVE_REPLIES) +            embed.description = f"Sorry, but you may only use this command within <#{Channels.bot}>." +            await ctx.send(embed=embed) + +        else: +            log.exception(f"Unhandled error: {error}") +  def setup(bot):      bot.add_cog(Information(bot)) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index b128b6de1..8ecd80127 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -149,7 +149,7 @@ class Tags:          tags = [] -        embed = Embed() +        embed: Embed = Embed()          embed.colour = Colour.red()          tag_data = await self.get_tag_data(tag_name) diff --git a/bot/decorators.py b/bot/decorators.py index 87877ecbf..80311243d 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -10,6 +10,7 @@ from discord.ext import commands  from discord.ext.commands import CheckFailure, Context  from bot.constants import ERROR_REPLIES +from bot.utils.checks import in_channel_check, with_role_check, without_role_check  log = logging.getLogger(__name__) @@ -47,35 +48,35 @@ def in_channel(*channels: int, bypass_roles: typing.Container[int] = None):  def with_role(*role_ids: int): -    async def predicate(ctx: Context): -        if not ctx.guild:  # Return False in a DM -            log.debug(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " -                      "This command is restricted by the with_role decorator. Rejecting request.") -            return False - -        for role in ctx.author.roles: -            if role.id in role_ids: -                log.debug(f"{ctx.author} has the '{role.name}' role, and passes the check.") -                return True +    """ +    Returns True if the user has any one +    of the roles in role_ids. +    """ -        log.debug(f"{ctx.author} does not have the required role to use " -                  f"the '{ctx.command.name}' command, so the request is rejected.") -        return False +    async def predicate(ctx: Context): +        return with_role_check(ctx, *role_ids)      return commands.check(predicate)  def without_role(*role_ids: int): +    """ +    Returns True if the user does not have any +    of the roles in role_ids. +    """ +      async def predicate(ctx: Context): -        if not ctx.guild:  # Return False in a DM -            log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " -                      "This command is restricted by the without_role decorator. Rejecting request.") -            return False +        return without_role_check(ctx, *role_ids) +    return commands.check(predicate) -        author_roles = [role.id for role in ctx.author.roles] -        check = all(role not in author_roles for role in role_ids) -        log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " -                  f"The result of the without_role check was {check}.") -        return check + +def in_channel(channel_id): +    """ +    Checks if the command was executed +    inside of the specified channel. +    """ + +    async def predicate(ctx: Context): +        return in_channel_check(ctx, channel_id)      return commands.check(predicate) diff --git a/bot/utils/checks.py b/bot/utils/checks.py new file mode 100644 index 000000000..37dc657f7 --- /dev/null +++ b/bot/utils/checks.py @@ -0,0 +1,56 @@ +import logging + +from discord.ext.commands import Context + +log = logging.getLogger(__name__) + + +def with_role_check(ctx: Context, *role_ids: int) -> bool: +    """ +    Returns True if the user has any one +    of the roles in role_ids. +    """ + +    if not ctx.guild:  # Return False in a DM +        log.trace(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " +                  "This command is restricted by the with_role decorator. Rejecting request.") +        return False + +    for role in ctx.author.roles: +        if role.id in role_ids: +            log.trace(f"{ctx.author} has the '{role.name}' role, and passes the check.") +            return True + +    log.trace(f"{ctx.author} does not have the required role to use " +              f"the '{ctx.command.name}' command, so the request is rejected.") +    return False + + +def without_role_check(ctx: Context, *role_ids: int) -> bool: +    """ +    Returns True if the user does not have any +    of the roles in role_ids. +    """ + +    if not ctx.guild:  # Return False in a DM +        log.trace(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " +                  "This command is restricted by the without_role decorator. Rejecting request.") +        return False + +    author_roles = (role.id for role in ctx.author.roles) +    check = all(role not in author_roles for role in role_ids) +    log.trace(f"{ctx.author} tried to call the '{ctx.command.name}' command. " +              f"The result of the without_role check was {check}.") +    return check + + +def in_channel_check(ctx: Context, channel_id: int) -> bool: +    """ +    Checks if the command was executed +    inside of the specified channel. +    """ + +    check = ctx.channel.id == channel_id +    log.trace(f"{ctx.author} tried to call the '{ctx.command.name}' command. " +              f"The result of the in_channel check was {check}.") +    return check  |