[commands] Implement a command cooldown system.

The way the command cooldown works is using a windowed way of doing it.
That is, if we have a cooldown of 2 commands every 30 seconds then if we
do a single command, we have 30 seconds to do the second command or else
we will get rate limited. This more or less matches the common
expectations on how cooldowns should be.

These cooldowns can be bucketed up to a single dimension of depth for
a per-user, per-guild, or per-channel basis. Of course, a global bucket
is also provided. These cannot be mixed, e.g. no per-channel per-user
cooldowns.

When a command cooldown is triggered, the error handlers will receive a
an exception of type CommandOnCooldown with proper information regarding
the cooldown such as retry_after and the bucket information itself.
This commit is contained in:
Rapptz
2016-07-22 18:05:38 -04:00
parent 5010e7dc55
commit cd0de57d13
4 changed files with 193 additions and 3 deletions

View File

@ -29,7 +29,7 @@ from discord.errors import DiscordException
__all__ = [ 'CommandError', 'MissingRequiredArgument', 'BadArgument',
'NoPrivateMessage', 'CheckFailure', 'CommandNotFound',
'DisabledCommand', 'CommandInvokeError', 'TooManyArguments',
'UserInputError' ]
'UserInputError', 'CommandOnCooldown' ]
class CommandError(DiscordException):
"""The base exception type for all command related errors.
@ -110,3 +110,18 @@ class CommandInvokeError(CommandError):
self.original = e
super().__init__('Command raised an exception: {0.__class__.__name__}: {0}'.format(e))
class CommandOnCooldown(CommandError):
"""Exception raised when the command being invoked is on cooldown.
Attributes
-----------
cooldown: Cooldown
A class with attributes ``rate``, ``per``, and ``type`` similar to
the :func:`cooldown` decorator.
retry_after: float
The amount of seconds to wait before you can retry again.
"""
def __init__(self, cooldown, retry_after):
self.cooldown = cooldown
self.retry_after = retry_after
super().__init__('You are on cooldown. Try again in {:.2f}s'.format(retry_after))