Cooldown system issue at discord.js

Viewed 544

I'm new at coding, I'm making a discord bot, and I'd like to make a command where all members need to wait a certain time before using it again, but some of them doesn't need to wait the same time.

For exemple: member with role X wait 20 seconds and member with role Y wait 10 seconds.

The problem is that I'm unable to make it work with a npm package called humanize-duration to make a time remaining. The embed description doesn't show the correct timestamp. Maybe it's a mistake made by me.

What I tried to fix my issue?

I tried to read the discord.js docs, humanize-duration docs, and this post but I couldn't fix it. Also, I don't get any errors. By the way, I use a command handler.

This is the code I use as exemple(ping command):

const Discord = require("discord.js");
const humanizeDuration = require("humanize-duration");

let cooldown = new Set();

module.exports = {
  name: "ping",
  description: "It shows the bot latency.",
  
  execute(client, message, args) {
      
    let guild = client.guilds.cache.get("828376495969402901");

    let member = guild.members.cache.get(message.author.id);
      
    if(cooldown.has(message.author.id) && !member.roles.cache.has("830503345251680298")) {
        const remaining = humanizeDuration(cooldown.has(message.author.id) && !member.roles.cache.has("830503345251680298") - Date.now(), { units: ["s"], round: true, language: "en" });
        const cooldownEmbed = new Discord.MessageEmbed()
        .setTitle(`Slow down, bud`)
        .setDescription(`You can run \`ping\` again in **${remaining}**.\nThe default cooldown is \`20s\`, but boosters can only wait \`10s\`!`)
        .setColor(`RANDOM`);
        
       return message.channel.send(cooldownEmbed)
    }else if (cooldown.has(message.author.id) && member.roles.cache.has("830503345251680298")) {
        const remaining = humanizeDuration(cooldown.has(message.author.id) && member.roles.cache.has("830503345251680298") - Date.now(), { units: ["s"], round: true, language: "en" });
        const cooldownEmbed = new Discord.MessageEmbed()
        .setTitle(`Too fast`)
        .setDescription(`You can run this command again in **${remaining}**. (booster perk)`)
        .setColor(`RANDOM`);
        
        return message.channel.send(cooldownEmbed)
    }
    
    if(cooldown.add(message.author.id) && !member.roles.cache.has("830503345251680298")) {
    setTimeout(() => {
        cooldown.delete(message.author.id) && !member.roles.cache.has("830503345251680298")
    }, 10000)
}else if(cooldown.add(message.author.id) && member.roles.cache.has("830503345251680298")) {
    setTimeout(() => {
        cooldown.delete(message.author.id) && member.roles.cache.has("830503345251680298")
    }, 2000)
}

    message.channel.send('Pinging...').then(message => {

      const embed = new Discord.MessageEmbed()
        .setDescription(`Pong! :ping_pong: \`${Date.now() - message.createdTimestamp} ms\``)
        .setColor(`RANDOM`);

      message.edit(" ", embed);
    })
  }
}

enter image description here

Thank you very much for the help in advance.

Kind Regards.

2 Answers

Problem #1: cooldown.has(message.author.id) && member.roles.cache.has("830503345251680298") does not return a number

Problem #2: it looks like the result returns a number in milliseconds so you need to work out the remaining time in milliseconds using the correct calculations. Instead of Date.now() you can use performance.now().

How it works:

const {performance} = require('perf_hooks');
 
var start = performance.now();
// something happens
var end = performance.now();
var total = Math.floor(end - start) // returns time between each event

possible solution:

const Discord = require("discord.js");
const humanizeDuration = require("humanize-duration");
const { performance } = require('perf_hooks');

let cooldown = new Set();

module.exports = {
    name: "ping",
    description: "It shows the bot latency.",

    execute(client, message, args) {

        let guild = client.guilds.cache.get("828376495969402901");
        var start = performance.now();
        let member = guild.members.cache.get(message.author.id);

        if (cooldown.has(message.author.id) && !member.roles.cache.has("830503345251680298")) {
            var end = performance.now();
            var total = Math.floor(end - start)
            const remaining = humanizeDuration(total, { units: ["s"], round: true, language: "en" });
            const cooldownEmbed = new Discord.MessageEmbed()
                .setTitle(`Slow down, bud`)
                .setDescription(`You can run \`ping\` again in **${remaining}**.\nThe default cooldown is \`20s\`, but boosters can only wait \`10s\`!`)
                .setColor(`RANDOM`);

            return message.channel.send(cooldownEmbed)
        }
    }
}

message.channel.send('Pinging...').then(message => {

    const embed = new Discord.MessageEmbed()
        .setDescription(`Pong! :ping_pong: \`${Date.now() - message.createdTimestamp} ms\``)
        .setColor(`RANDOM`);

    message.edit(" ", embed);
})

There are a couple of errors. First, cooldown.has(message.author.id) and member.roles.cache.has("830503345251680298") are both returning a boolean. You can't use it to measure the remaining time.

The error shown on your image is coming from the fact that you're trying to subtract a date (Date.now()) from a boolean (!member.roles.cache.has("830503345251680298")), so it returns the number of milliseconds since the epoch time. Check the snippet below:

console.log(true - Date.now())
console.log(false - Date.now())

To solve this, you need to store the expiry date somewhere. I suggest you to store these cooldown expire times with a key of the member ID in a discord collection. Then, check two things; if the member has a booster role (memberHasBooster) and if the member has to wait for the cooldown to expire (memberHasCooldown).

If the memberHasCooldown, you can calculate the time remaining by subtracting current date (new Date()) from the expiry date (cooldowns.get(message.author.id)). It returns the remaining time in milliseconds. Notice that I used cooldowns.get, not cooldowns.has. Collection#get returns the actual value stored, Collection#has returns a boolean only.

Then, you can format the remaining time, and send it in an embed.

If there is no cooldown for the member, you can add a new one by using Collection#set. The key will be the member ID, and the value is the expiry date. You can calculate the expiry date by adding 10000/20000 milliseconds (timeout) to the current time. You also need to add a setTimeout with the same timeout and delete the cooldown form the collection.

Once you added the cooldown, you can send the actual message. I used async/await here. Don't forget to use a different name for the actual sent message (sentMessage instead of message).

Check out the full code below:

let Discord = require('discord.js');
let humanizeDuration = require('humanize-duration');

let cooldowns = new Discord.Collection();

module.exports = {
  name: 'ping',
  description: 'It shows the bot latency.',
  async execute(client, message, args) {
      let guild = client.guilds.cache.get('828376495969402901');
      let member = guild.members.cache.get(message.author.id);
      let memberHasBooster = member.roles.cache.has('830503345251680298');
      let memberHasCooldown = cooldowns.has(message.author.id);

      if (memberHasCooldown) {
        let remaining = cooldowns.get(message.author.id) - new Date.now();
        let remainingFormatted = humanizeDuration(remaining, {
          language: 'en',
          round: true,
          units: ['s'],
        });
        let embed = new Discord.MessageEmbed()
          .setColor(`RANDOM`)
          .setTitle(`Slow down, bud`)
          .setDescription(
            `You can run \`ping\` again in **${remainingFormatted}**. ${
              memberHasBooster
                ? '(booster perk)'
                : '\nThe default cooldown is `20s`, but boosters can only wait `10s`!'
            }`,
          );

        return message.channel.send(embed);
      }

      // add cooldown
      const timeout = memberHasBooster ? 10 * 1000 : 20 * 1000;
      cooldowns.set(message.author.id, new Date() + timeout);

      setTimeout(() => cooldowns.delete(message.author.id), timeout);

      const sentMessage = await message.channel.send('Pinging...');

      const embed = new Discord.MessageEmbed()
        .setDescription(
          `Pong! :ping_pong: \`${
            Date.now() - sentMessage.createdTimestamp
          } ms\``,
        )
        .setColor(`RANDOM`);

      sentMessage.edit(' ', embed);
  },
};
Related