Hyperba
Hyperba

Reputation: 119

How do I set a double/advanced cooldown for a command in discord.py?

This is a basic command with a basic cooldown:

@client.command()
@commands.cooldown(5, 57600, commands.BucketType.user)
async def ping(ctx)
   await ctx.send("pong")

So basically, when a user uses the prefix of the command (for example '!k') then types "ping" the bot will send "pong!" to the chat. This is a very basic command, but then comes the cooldown decorator. From the cooldown:

@commands.cooldown(5, 57600, commands.BucketType.user)

we can tell that the command can be used 5 times between 57600 seconds (16 hours) but what if I want a command to be used only 5 times per 16 hours AND there's ANOTHER cooldown between each time you use the command? Let me explain...

You can use the command 5 times per 16 hours

But when you use the command, you have to wait 1 hour to use it again

That way, you can not use the command 5 times straight away in under a minute

Think of it like a tree that grows an apple every hour but only grows 5 apples a day... How can I prevent a user from using the command 5 times straight away? Thanks -

Upvotes: 1

Views: 901

Answers (1)

Eric Jin
Eric Jin

Reputation: 3924

Discord.py does not allow for multiple cooldown checks per command, so you will have to use a custom cooldown handler.

class CooldownManager:

    def __init__(self, executions_allowed: int, cooldown_time: float):
        self.state = {}
        self.executions_allowed = executions_allowed
        self.cooldown_time = cooldown_time

    def time_left(self, key) -> float:
        """Attempt to execute. Return 0 if ready, or the number of seconds until you're allowed to execute again."""
        if key not in self.state.keys():
            self.state[key] = []
        if len(self.state[key]) > 0:
            # Clean up executions that have aged away
            # self.state[key] is sorted with the newest first
            for i in range(len(self.state[key])-1, -1, -1):
                if self.state[key][i] + self.cooldown_time < time.time():
                    del self.state[key][i]
            if len(self.state[key]) < self.executions_allowed:
                self.state[key].append(time.time())
                return 0
            next_available_execution = self.state[key][len(self.state)-1] + self.cooldown_time
            return next_available_execution - time.time()
        else:
            self.state[key].append(time.time())
            return 0

    def assert_cooldown(self, data):
        """Run this at the beginning of the command."""
        time_left = self.time_left(data)
        if time_left > 0:
            raise commands.CommandOnCooldown('', retry_after=time_left)


cm1 = CooldownManager(1, 4.0)  # execute once every 4 seconds
cm2 = CooldownManager(3, 120.0)  # execute up to 3x every 2 minutes
@client.command(name='test')
async def multicooldown(ctx):
    # Check both cooldowns. This raises `CommandOnCooldown` just like the vanilla cooldown handler, so you can catch that later in your command error check.
    cm1.assert_cooldown(ctx.author.id)  # This is basically the bucket type. You can use things like ctx.author.id, ctx.guild.id, etc
    cm2.assert_cooldown(ctx.author.id)
    # Do whatever you want here
    await ctx.send('test')

Upvotes: 2

Related Questions