diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index 5d5601b27..c70ade1ef 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -1302,6 +1302,15 @@ def has_role(item): If the message is invoked in a private message context then the check will return ``False``. + This check raises one of two special exceptions, :exc:`.MissingRole` if the user + is missing a role, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1.0 + + Raise :exc:`.MissingRole` or :exc:`.NoPrivateMessage` + instead of generic :exc:`.CheckFailure` + Parameters ----------- item: Union[:class:`int`, :class:`str`] @@ -1310,13 +1319,15 @@ def has_role(item): def predicate(ctx): if not isinstance(ctx.channel, discord.abc.GuildChannel): - return False + raise NoPrivateMessage() if isinstance(item, int): role = discord.utils.get(ctx.author.roles, id=item) else: role = discord.utils.get(ctx.author.roles, name=item) - return role is not None + if role is None: + raise MissingRole(item) + return True return check(predicate) @@ -1327,6 +1338,15 @@ def has_any_role(*items): Similar to :func:`.has_role`\, the names or IDs passed in must be exact. + This check raises one of two special exceptions, :exc:`.MissingAnyRole` if the user + is missing all roles, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1.0 + + Raise :exc:`.MissingAnyRole` or :exc:`.NoPrivateMessage` + instead of generic :exc:`.CheckFailure` + Parameters ----------- items: List[Union[:class:`str`, :class:`int`]] @@ -1344,10 +1364,67 @@ def has_any_role(*items): """ def predicate(ctx): if not isinstance(ctx.channel, discord.abc.GuildChannel): - return False + raise NoPrivateMessage() getter = functools.partial(discord.utils.get, ctx.author.roles) - return any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items) + if any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items): + return True + raise MissingAnyRole(items) + + return check(predicate) + +def bot_has_role(item): + """Similar to :func:`.has_role` except checks if the bot itself has the + role. + + This check raises one of two special exceptions, :exc:`.BotMissingRole` if the bot + is missing the role, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1.0 + + Raise :exc:`.BotMissingRole` or :exc:`.NoPrivateMessage` + instead of generic :exc:`.CheckFailure` + """ + + def predicate(ctx): + ch = ctx.channel + if not isinstance(ch, discord.abc.GuildChannel): + raise NoPrivateMessage() + + me = ch.guild.me + if isinstance(item, int): + role = discord.utils.get(me.roles, id=item) + else: + role = discord.utils.get(me.roles, name=item) + if role is None: + raise BotMissingRole(item) + return True + return check(predicate) + +def bot_has_any_role(*items): + """Similar to :func:`.has_any_role` except checks if the bot itself has + any of the roles listed. + + This check raises one of two special exceptions, :exc:`.BotMissingAnyRole` if the bot + is missing all roles, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1.0 + + Raise :exc:`.BotMissingAnyRole` or :exc:`.NoPrivateMessage` + instead of generic checkfailure + """ + def predicate(ctx): + ch = ctx.channel + if not isinstance(ch, discord.abc.GuildChannel): + raise NoPrivateMessage() + + me = ch.guild.me + getter = functools.partial(discord.utils.get, me.roles) + if any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items): + return True + raise BotMissingAnyRole(items) return check(predicate) def has_permissions(**perms): @@ -1389,36 +1466,6 @@ def has_permissions(**perms): return check(predicate) -def bot_has_role(item): - """Similar to :func:`.has_role` except checks if the bot itself has the - role. - """ - - def predicate(ctx): - ch = ctx.channel - if not isinstance(ch, discord.abc.GuildChannel): - return False - me = ch.guild.me - if isinstance(item, int): - role = discord.utils.get(me.roles, id=item) - else: - role = discord.utils.get(me.roles, name=item) - return role is not None - return check(predicate) - -def bot_has_any_role(*items): - """Similar to :func:`.has_any_role` except checks if the bot itself has - any of the roles listed. - """ - def predicate(ctx): - ch = ctx.channel - if not isinstance(ch, discord.abc.GuildChannel): - return False - me = ch.guild.me - getter = functools.partial(discord.utils.get, me.roles) - return any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items) - return check(predicate) - def bot_has_permissions(**perms): """Similar to :func:`.has_permissions` except checks if the bot itself has the permissions listed. @@ -1469,7 +1516,7 @@ def guild_only(): def predicate(ctx): if ctx.guild is None: - raise NoPrivateMessage('This command cannot be used in private messages.') + raise NoPrivateMessage() return True return check(predicate) @@ -1492,9 +1539,20 @@ def is_owner(): return check(predicate) def is_nsfw(): - """A :func:`.check` that checks if the channel is a NSFW channel.""" + """A :func:`.check` that checks if the channel is a NSFW channel. + + This check raises a special exception, :exc:`.NSFWChannelRequired` + that is derived from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1.0 + + Raise :exc:`.NSFWChannelRequired instead of generic :exc:`.CheckFailure`.` + """ def pred(ctx): - return isinstance(ctx.channel, discord.TextChannel) and ctx.channel.is_nsfw() + ch = ctx.channel + if isinstance(ch, discord.TextChannel) and ch.is_nsfw(): + return True + raise NSFWChannelRequired(ch) return check(pred) def cooldown(rate, per, type=BucketType.default): diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index b98014e37..760daf6b4 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -29,11 +29,12 @@ from discord.errors import DiscordException __all__ = ['CommandError', 'MissingRequiredArgument', 'BadArgument', 'PrivateMessageOnly', 'NoPrivateMessage', 'CheckFailure', - 'CommandNotFound' ,'DisabledCommand', 'CommandInvokeError', - 'TooManyArguments', 'UserInputError', 'CommandOnCooldown', - 'NotOwner', 'MissingPermissions', 'BotMissingPermissions', - 'ConversionError', 'BadUnionArgument', 'ArgumentParsingError', - 'UnexpectedQuoteError', 'InvalidEndOfQuotedStringError', + 'CommandNotFound', 'DisabledCommand', 'CommandInvokeError', + 'TooManyArguments','UserInputError', 'CommandOnCooldown', + 'NotOwner', 'MissingRole', 'BotMissingRole', 'MissingAnyRole', + 'BotMissingAnyRole','MissingPermissions', 'BotMissingPermissions', + 'NSFWChannelRequired', 'ConversionError', 'BadUnionArgument', + 'ArgumentParsingError', 'UnexpectedQuoteError', 'InvalidEndOfQuotedStringError', 'ExpectedClosingQuoteError', 'ExtensionError', 'ExtensionAlreadyLoaded', 'ExtensionNotLoaded', 'NoEntryPointError', 'ExtensionFailed', 'ExtensionNotFound'] @@ -128,7 +129,9 @@ class NoPrivateMessage(CheckFailure): """Exception raised when an operation does not work in private message contexts. """ - pass + + def __init__(self): + super().__init__('This command cannot be used in private messages.') class NotOwner(CheckFailure): """Exception raised when the message author is not the owner of the bot.""" @@ -167,8 +170,116 @@ class CommandOnCooldown(CommandError): self.retry_after = retry_after super().__init__('You are on cooldown. Try again in {:.2f}s'.format(retry_after)) +class MissingRole(CheckFailure): + """Exception raised when the command invoker lacks a role to run a command. + + This inherits from :exc:`.CheckFailure` + + .. versionadded:: 1.1.0 + + Attributes + ----------- + missing_role: Union[:class:`str`, :class:`int`] + The required role that is missing. + This is the parameter passed to :func:`~.commands.has_role`. + """ + def __init__(self, missing_role): + self.missing_role = missing_role + message = 'Role {0!r} is required to run this command.'.format(missing_role) + super().__init__(message) + +class BotMissingRole(CheckFailure): + """Exception raised when the bot's member lacks a role to run a command. + + This inherits from :exc:`.CheckFailure` + + .. versionadded:: 1.1.0 + + Attributes + ----------- + missing_role: Union[:class:`str`, :class:`int`] + The required role that is missing. + This is the parameter passed to :func:`~.commands.has_role`. + """ + def __init__(self, missing_role): + self.missing_role = missing_role + message = 'Bot requires the role {0!r} to run this command'.format(missing_role) + super().__init__(message) + +class MissingAnyRole(CheckFailure): + """Exception raised when the command invoker lacks any of + the roles specified to run a command. + + This inherits from :exc:`.CheckFailure` + + .. versionadded:: 1.1.0 + + Attributes + ----------- + missing_roles: List[Union[:class:`str`, :class:`int`]] + The roles that the invoker is missing. + These are the parameters passed to :func:`~.commands.has_any_role`. + """ + def __init__(self, missing_roles): + self.missing_roles = missing_roles + + missing = ["'{}'".format(role) for role in missing_roles] + + if len(missing) > 2: + fmt = '{}, or {}'.format(", ".join(missing[:-1]), missing[-1]) + else: + fmt = ' or '.join(missing) + + message = "You are missing at least one of the required roles: {}".format(fmt) + super().__init__(message) + + +class BotMissingAnyRole(CheckFailure): + """Exception raised when the bot's member lacks any of + the roles specified to run a command. + + This inherits from :exc:`.CheckFailure` + + .. versionadded:: 1.1.0 + + Attributes + ----------- + missing_roles: List[Union[:class:`str`, :class:`int`]] + The roles that the bot's member is missing. + These are the parameters passed to :func:`~.commands.has_any_role`. + + """ + def __init__(self, missing_roles): + self.missing_roles = missing_roles + + missing = ["'{}'".format(role) for role in missing_roles] + + if len(missing) > 2: + fmt = '{}, or {}'.format(", ".join(missing[:-1]), missing[-1]) + else: + fmt = ' or '.join(missing) + + message = "Bot is missing at least one of the required roles: {}".format(fmt) + super().__init__(message) + +class NSFWChannelRequired(CheckFailure): + """Exception raised when a channel does not have the required NSFW setting. + + This inherits from :exc:`.CheckFailure`. + + .. versionadded:: 1.1.0 + + Parameters + ----------- + channel: :class:`discord.abc.GuildChannel` + The channel that does not have NSFW enabled. + """ + def __init__(self, channel): + self.channel = channel + super().__init__("Channel '{}' needs to be NSFW for this command to work.".format(channel)) + class MissingPermissions(CheckFailure): - """Exception raised when the command invoker lacks permissions to run + """Exception raised when the command invoker lacks permissions to run a command. Attributes @@ -185,11 +296,12 @@ class MissingPermissions(CheckFailure): fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1]) else: fmt = ' and '.join(missing) - message = 'You are missing {} permission(s) to run command.'.format(fmt) + message = 'You are missing {} permission(s) to run this command.'.format(fmt) super().__init__(message, *args) class BotMissingPermissions(CheckFailure): - """Exception raised when the bot lacks permissions to run command. + """Exception raised when the bot's member lacks permissions to run a + command. Attributes ----------- @@ -205,7 +317,7 @@ class BotMissingPermissions(CheckFailure): fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1]) else: fmt = ' and '.join(missing) - message = 'Bot requires {} permission(s) to run command.'.format(fmt) + message = 'Bot requires {} permission(s) to run this command.'.format(fmt) super().__init__(message, *args) class BadUnionArgument(UserInputError): diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 166fdb1df..3400efe04 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -255,6 +255,9 @@ Exceptions .. autoexception:: discord.ext.commands.BadUnionArgument :members: +.. autoexception:: discord.ext.commands.PrivateMessageOnly + :members: + .. autoexception:: discord.ext.commands.NoPrivateMessage :members: @@ -288,6 +291,21 @@ Exceptions .. autoexception:: discord.ext.commands.BotMissingPermissions :members: +.. autoexception:: discord.ext.commands.MissingRole + :members: + +.. autoexception:: discord.ext.commands.BotMissingRole + :members: + +.. autoexception:: discord.ext.commands.MissingAnyRole + :members: + +.. autoexception:: discord.ext.commands.BotMissingAnyRole + :members: + +.. autoexception:: discord.ext.commands.NSFWChannelRequired + :members: + .. autoexception:: discord.ext.commands.ExtensionError :members: @@ -326,10 +344,16 @@ Exception Hierarchy - :exc:`~.commands.ExpectedClosingQuoteError` - :exc:`~.commands.CommandNotFound` - :exc:`~.commands.CheckFailure` + - :exc:`~.commands.PrivateMessageOnly` - :exc:`~.commands.NoPrivateMessage` - :exc:`~.commands.NotOwner` - :exc:`~.commands.MissingPermissions` - :exc:`~.commands.BotMissingPermissions` + - :exc:`~.commands.MissingRole` + - :exc:`~.commands.BotMissingRole` + - :exc:`~.commands.MissingAnyRole` + - :exc:`~.commands.BotMissingAnyRole` + - :exc:`~.commands.NSFWChannelRequired` - :exc:`~.commands.DisabledCommand` - :exc:`~.commands.CommandInvokeError` - :exc:`~.commands.CommandOnCooldown`