Quando um bot do Discord responde a todos os comandos instantaneamente sem qualquer atraso, os usuários podem enviar spam e sobrecarregar os recursos do bot. Um sistema de cooldown evita isso aplicando um período de espera entre comandos para cada usuário. Este artigo explica como implementar limites de cooldown por usuário em um bot do Discord usando Discord.js v14 e Node.js. Você aprenderá o conceito central de cooldowns, verá um exemplo de código funcional e entenderá como evitar armadilhas comuns, como vazamentos de memória e cálculos incorretos de tempo.
Principais Conclusões: Implementando Cooldowns por Usuário em Bots do Discord
- Mapa de cooldown baseado em Collection: Use um
MapouCollectionpara armazenar o timestamp do último comando de cada usuário. - Comparação de timestamps: Subtraia o timestamp armazenado de
Date.now()e compare com a duração do cooldown em milissegundos. - Resposta de erro com tempo restante: Envie uma mensagem mostrando quantos segundos faltam para o usuário poder usar o comando novamente.
Como Funciona um Cooldown por Usuário no Discord.js
Um cooldown por usuário limita a frequência com que um único usuário pode executar um comando específico. O bot armazena o horário em que o usuário executou o último comando. Quando o usuário executa o mesmo comando novamente, o bot verifica se tempo suficiente já passou. Se não, o bot recusa o comando e informa ao usuário quanto tempo deve esperar.
A abordagem mais comum usa um objeto Map do JavaScript ou Collection do Discord.js. A chave é o ID do usuário, e o valor é um timestamp em milissegundos. Quando um comando é executado, o bot armazena Date.now(). Na próxima invocação, o bot compara Date.now() com o valor armazenado. Se a diferença for menor que a duração do cooldown, o comando é bloqueado.
Este método funciona tanto para comandos de barra quanto para comandos de prefixo. Para comandos de barra, a verificação de cooldown ocorre dentro da função execute. Para comandos de prefixo, a verificação ocorre dentro do manipulador de eventos messageCreate. A mesma lógica se aplica a ambos.
Pré-requisitos
Antes de implementar cooldowns, certifique-se de ter:
- Node.js versão 16.9.0 ou superior instalado
- Um aplicativo de bot do Discord criado no Portal do Desenvolvedor do Discord
- Discord.js v14 instalado (
npm install discord.js@14) - Conhecimento básico de JavaScript assíncrono e manipulação de eventos
Passos para Implementar Cooldowns por Usuário em um Comando de Barra
Os passos a seguir mostram como adicionar um cooldown de 10 segundos a um comando de barra. O bot armazena cooldowns em um Collection global e verifica timestamps antes de executar a lógica do comando.
- Crie um novo Collection no arquivo principal do bot
Adicione esta linha no topo do seuindex.jsou arquivo principal:const cooldowns = new Discord.Collection();. Este collection armazenará IDs de usuários e seus timestamps de cooldown para cada comando. - Defina a duração do cooldown em milissegundos
Dentro do manipulador de comandos ou diretamente no arquivo do comando, defina uma constante:const cooldownDuration = 10000;. Isso equivale a 10 segundos. Ajuste este número conforme necessário. - Verifique se o usuário já possui uma entrada de cooldown
Dentro da funçãoexecutedo seu comando de barra, useif (!cooldowns.has(commandName)) cooldowns.set(commandName, new Discord.Collection());. Isso garante que cada comando tenha seu próprio collection de cooldown. - Obtenha o timestamp atual e o timestamp armazenado para o usuário
Escreva:const now = Date.now();econst timestamps = cooldowns.get(commandName);. Em seguida, obtenha o timestamp armazenado do usuário:const cooldownAmount = cooldownDuration;econst userTimestamp = timestamps.get(interaction.user.id);. - Compare timestamps e bloqueie se ainda estiver em cooldown
Escreva:if (userTimestamp) { const expirationTime = userTimestamp + cooldownAmount; if (now < expirationTime) { const timeLeft = (expirationTime - now) / 1000; return interaction.reply({ content: `Aguarde ${timeLeft.toFixed(1)} segundos antes de usar este comando novamente.`, ephemeral: true }); } }. Isso envia uma resposta privada mostrando o tempo restante. - Defina o novo timestamp após o comando ser executado
Após a lógica do comando ser executada, escreva:timestamps.set(interaction.user.id, now);. Isso atualiza o cooldown para que o usuário tenha que esperar novamente. Opcionalmente, adicione um timeout para limpar entradas antigas:setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount);.
Após seguir estes passos, o comando de barra recusará a execução se o usuário executá-lo mais de uma vez a cada 10 segundos.
Passos para Implementar Cooldowns por Usuário em um Comando de Prefixo
Comandos de prefixo funcionam de forma diferente porque dependem do evento messageCreate. A lógica de cooldown é semelhante, mas você usa message.author.id em vez de interaction.user.id.
- Crie o Collection de cooldown fora do manipulador de eventos
No arquivo principal do bot, adicione:const cooldowns = new Discord.Collection();. Este collection armazena timestamps para cada nome de comando. - Dentro do evento messageCreate, verifique o prefixo do comando
Escreva:if (!message.content.startsWith(prefix) || message.author.bot) return;. Extraia o nome do comando e argumentos como de costume. - Inicialize o collection de cooldown para este comando
Escreva:if (!cooldowns.has(commandName)) cooldowns.set(commandName, new Discord.Collection());. - Obtenha timestamps e verifique o cooldown
Escreva:const now = Date.now(); const timestamps = cooldowns.get(commandName); const cooldownAmount = 10000; const userTimestamp = timestamps.get(message.author.id);. Em seguida, compare:if (userTimestamp) { const expirationTime = userTimestamp + cooldownAmount; if (now < expirationTime) { const timeLeft = (expirationTime - now) / 1000; return message.reply(`Aguarde ${timeLeft.toFixed(1)} segundos antes de usar este comando novamente.`); } }. - Defina o novo timestamp e opcionalmente limpe
Escreva:timestamps.set(message.author.id, now); setTimeout(() => timestamps.delete(message.author.id), cooldownAmount);.
Agora seu comando de prefixo também aplica um cooldown de 10 segundos por usuário.
Erros Comuns e Limitações a Evitar
Cooldown Não Persiste Após Reinicialização do Bot
O Collection em memória é perdido quando o bot reinicia. Os usuários podem executar comandos imediatamente após uma reinicialização. Para persistir cooldowns entre reinicializações, armazene os timestamps em um banco de dados como SQLite ou Redis. Para a maioria dos bots pequenos, a abordagem em memória é suficiente.
Cooldown se Aplica a Todos os Comandos em Vez de Por Comando
Se você usar um único collection para todos os comandos, o cooldown de um usuário para um comando bloqueia todos os outros comandos. A correção é aninhar collections: um collection por nome de comando, cada um contendo timestamps de usuários. Os exemplos de código acima já implementam isso corretamente.
Erros de Cálculo de Tempo Devido à Deriva do Relógio do Servidor
Date.now() usa o relógio do sistema do servidor. Se o relógio sofrer deriva ou for alterado manualmente, os cooldowns podem expirar antes ou depois do previsto. Use process.hrtime.bigint() para tempo monotônico se precisar de alta precisão. Para a maioria dos bots, Date.now() é aceitável.
Vazamento de Memória por Entradas de Cooldown Não Utilizadas
Se você nunca excluir entradas de cooldown antigas, o Collection cresce indefinidamente. Sempre use setTimeout para excluir a entrada após o cooldown expirar. O exemplo acima inclui esta etapa de limpeza.
Comparação de Métodos de Armazenamento de Cooldown
| Método de Armazenamento | Collection em Memória | Banco de Dados (SQLite / Redis) |
|---|---|---|
| Persistência | Perdida na reinicialização | Sobrevive à reinicialização |
| Desempenho | Muito rápido, sem I/O | Mais lento devido a I/O |
| Complexidade | Simples, sem dependências externas | Requer configuração de banco e consultas |
| Caso de uso | Bots pequenos com baixo tráfego | Bots grandes ou cooldowns entre servidores |
Você pode implementar cooldowns por usuário usando a abordagem de Collection em memória abordada neste artigo. O sistema de cooldown usa um mapa de IDs de usuários para timestamps, compara-os a cada invocação de comando e bloqueia o comando até que o tempo de espera expire. Para o próximo passo, adicione uma duração de cooldown configurável por comando usando um arquivo JSON ou banco de dados. Para uso avançado, considere implementar um cooldown global que se aplique a vários servidores ou comandos usando Redis.