Wenn ein Discord-Bot wächst, ist eines der häufigsten Probleme die Befehlsflut: Ein Nutzer löst denselben Befehl Dutzende Male pro Sekunde aus, und der Bot antwortet jedes Mal aufs Neue. Ein solides Discord-Bot-Cooldown-System ist genau das, was dies löst; es gibt jedem Nutzer eine kurze Wartezeit, bevor er einen Befehl erneut ausführen kann, und schützt sowohl deinen Bot als auch die API-Limits von Discord. In diesem Leitfaden bauen wir die Cooldown-Logik von Grund auf, schreiben ein funktionierendes discord.js-Beispiel und ergänzen zusätzliche Schichten gegen Spam.
Warum du Cooldowns und Spam-Schutz brauchst
Ein Cooldown ist nicht nur eine Funktion, um „den Nutzer auszubremsen"; er ist eine Verteidigungslinie für die Stabilität deines Bots. Die Hauptgründe:
- API-Rate-Limits: Discord begrenzt, wie viele Anfragen ein Bot senden darf. Sendest du zu viele Nachrichten/Antworten, erhältst du eine
429 Too Many Requests-Antwort und eine vorübergehende Sperre. - Ressourcenverbrauch: Teure Befehle wie eine Datenbankabfrage, ein externer API-Aufruf oder Bildgenerierung können deinen Server unter Spam blockieren.
- Missbrauch: Ohne Cooldowns werden Wirtschafts-, Gewinnspiel- oder Belohnungsbefehle ausgenutzt.
- Kanal-Überflutung: Wiederholte Bot-Antworten machen Kanäle unlesbar.
Die Cooldown-Logik: einen Zeitstempel speichern
Jedes Cooldown-System beruht auf einer einzigen Idee: speichern, wann ein Nutzer den Befehl zuletzt ausgeführt hat und prüfen, ob genug Zeit vergangen ist. Der Pseudocode funktioniert so:
- Der Nutzer führt den Befehl aus.
- Prüfen, ob für diesen Nutzer ein gespeicherter Zeitstempel existiert.
- Falls vorhanden und
(jetzt - gespeichert) < Cooldown: ablehnen und die verbleibende Zeit mitteilen. - Andernfalls: den Befehl ausführen und einen neuen Zeitstempel speichern.
Für die Speicherung reicht der Arbeitsspeicher (eine Map) bei kleinen Bots aus. Wenn die Cooldowns beim Neustart des Bots nicht zurückgesetzt werden sollen, verwende Redis oder eine Datenbank.
Ein Cooldown-System pro Befehl mit discord.js
In discord.js v14 ist der gängige und offiziell empfohlene Ansatz für Slash-Befehle, pro Befehl eine eigene Collection zu führen. Collection erweitert die Map-Klasse von JavaScript. Du kannst deiner Befehlsdatei ein optionales cooldown-Feld hinzufügen:
// commands/ping.js
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
cooldown: 5, // Sekunden
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Zeigt die Latenz des Bots'),
async execute(interaction) {
await interaction.reply(`Pong! ${interaction.client.ws.ping}ms`);
},
};
Die eigentliche Prüfung erfolgt im interactionCreate-Event, bevor der Befehl ausgeführt wird:
// events/interactionCreate.js
const { Events, Collection } = require('discord.js');
module.exports = {
name: Events.InteractionCreate,
async execute(interaction) {
if (!interaction.isChatInputCommand()) return;
const command = interaction.client.commands.get(interaction.commandName);
if (!command) return;
const { cooldowns } = interaction.client;
if (!cooldowns.has(command.data.name)) {
cooldowns.set(command.data.name, new Collection());
}
const now = Date.now();
const timestamps = cooldowns.get(command.data.name);
const cooldownAmount = (command.cooldown ?? 3) * 1000;
if (timestamps.has(interaction.user.id)) {
const expiration = timestamps.get(interaction.user.id) + cooldownAmount;
if (now < expiration) {
const expiredTimestamp = Math.round(expiration / 1000);
return interaction.reply({
content: `Bitte warte, bevor du diesen Befehl erneut nutzt. Du kannst ihn <t:${expiredTimestamp}:R> wieder verwenden.`,
ephemeral: true,
});
}
}
timestamps.set(interaction.user.id, now);
setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount);
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
}
},
};
Vergiss nicht, die client.cooldowns-Collection einmal beim Start zu definieren: Füge client.cooldowns = new Collection(); hinzu, wenn du deinen Client einrichtest. <t:...:R> ist Discords relatives Zeitstempelformat und zeigt dem Nutzer einen sich automatisch aktualisierenden Text wie „in 3 Sekunden".
Cooldown-Typen: Nutzer, Server und global
Das obige Beispiel wendet einen Cooldown pro Nutzer an, was der häufigste Bedarf ist. Doch verschiedene Szenarien erfordern verschiedene Schlüssel:
- Pro Nutzer: Verwende
interaction.user.idals Schlüssel. Ideal für persönliche Befehle (Profil, Guthaben). - Pro Server: Verwende
interaction.guildIdals Schlüssel. Sinnvoll für Befehle, die den ganzen Server betreffen (Ankündigungen, Massenoperationen). - Pro Kanal: Begrenze bestimmte Kanäle mit
interaction.channelId. - Global: Verwende einen festen Schlüssel (z. B. den Befehlsnamen) für Befehle, die eine teure externe API schützen.
Du kannst dieselbe Logik wiederverwenden, indem du nur änderst, welches Feld du als Schlüssel speicherst. Um mehrere Typen zu kombinieren, baue einen zusammengesetzten Schlüssel: `${interaction.guildId}-${interaction.user.id}`.
Discords eigene Rate-Limits (429) und discord.js
Dein eigener Cooldown verhindert, dass Nutzer den Bot spammen; aber der Bot selbst unterliegt ebenfalls Limits gegenüber der Discord-API. Die gute Nachricht: Die @discordjs/rest-Schicht unter discord.js verwaltet globale und routenbasierte Rate-Limits automatisch, stellt Anfragen in eine Warteschlange und wartet bei einem 429. Dennoch gibt es Punkte, die du kennen solltest:
- Sende nicht Hunderte Nachrichten in einer Schleife; bündle die Operationen oder füge eine Verzögerung ein.
- Die Verwendung von
ephemeral: truebei flüchtigen Antworten (nur für die Person sichtbar, die den Befehl ausführte) reduziert Kanal-Überflutung und unnötigen Nachrichtenverkehr. - Rufe bei länger laufenden Aufgaben zuerst
interaction.deferReply()auf; sonst läufst du in den 3-Sekunden-Interaktions-Timeout. - Wenn du doch ein globales Rate-Limit erreichst, höre auf das
rateLimited-Event, um es zu protokollieren und dein Anfragevolumen zu senken.
Zusätzliche Maßnahmen gegen Spam
Ein Cooldown ist die erste Verteidigungslinie; gegen aggressiven Missbrauch füge diese Schichten hinzu:
- Eskalierende Strafe: Erhöhe vorübergehend die Wartezeit für einen Nutzer, der wiederholt gegen den Cooldown läuft.
- Beobachtungsliste: Ignoriere einen Nutzer kurzzeitig, sobald er eine Schwelle an Befehlsversuchen überschreitet (ein einfacher Rate-Limiter).
- Berechtigungsprüfung: Füge eine
interaction.memberPermissions-Prüfung hinzu, um Administratoren von Cooldowns auszunehmen. - Persistente Speicherung: Halte Cooldowns für Belohnungs-/Wirtschaftsbefehle in Redis oder einer Datenbank, damit sie nach einem Neustart nicht missbraucht werden können.
Häufige Fragen
Ist es sicher, Cooldown-Daten im Arbeitsspeicher zu halten?
Für kleine und mittlere Bots ist der Arbeitsspeicher (eine Collection/Map) schnell und ausreichend. Allerdings wird er bei jedem Neustart des Bots zurückgesetzt. Für missbrauchsanfällige Befehle wie Gewinnspiele, Belohnungen oder Wirtschaft empfehle ich, Cooldowns dauerhaft in Redis oder einer Datenbank zu speichern.
Wenn discord.js die Rate-Limits für mich übernimmt, warum überhaupt einen Cooldown schreiben?
Sie dienen unterschiedlichen Zwecken. Die REST-Schicht von discord.js reguliert die Anfragen, die der Bot an Discord sendet. Dein Cooldown steuert, wie oft Nutzer den Bot auslösen können; diese Logik verhindert Spam, Ressourcenverbrauch und Missbrauch von Spielmechaniken.
Gilt dieselbe Logik für discord.py?
Ja, das Prinzip ist identisch: die letzte Nutzungszeit speichern und die Differenz prüfen. discord.py hat zusätzlich einen eingebauten @commands.cooldown-Dekorator und BucketType-Optionen, mit denen du einen Cooldown pro Nutzer/Server/Kanal in einer einzigen Zeile definieren kannst.
Bricht dein Bot unter Spam zusammen? Ich kann dir helfen, ihn mit Befehls-Cooldowns, Rate-Limit-Handling und einer missbrauchsresistenten Architektur abzuhärten. Nimm Kontakt auf und lass uns über dein Projekt sprechen.