Eine der meistgewünschten Funktionen in einem Community-Bot ist ein System für geplante Discord-Nachrichten: ein Bot, der täglich zu einer festen Uhrzeit eine Ankündigung postet, am Wochenende eine Event-Erinnerung sendet oder um Mitternacht eine „Gute Nacht"-Nachricht absetzt. In dieser Anleitung bauen wir ein solides Setup, das automatische Nachrichten zu einem bestimmten Zeitpunkt mit discord.js und node-cron versendet, und gehen Zeitzonen, persistente Speicherung und häufige Fallstricke Schritt für Schritt durch.
Der Ansatz: warum node-cron?
Die erste Idee, um zu einer festen Uhrzeit zu posten, ist setTimeout oder setInterval, aber die arbeiten mit relativen Intervallen wie „alle 24 Stunden"; sie können keine kalenderbasierte Regel wie „jeden Tag um 09:00" ausdrücken. Genau hier glänzt node-cron: Es führt die Linux-Cron-Syntax innerhalb von Node.js aus, sodass du Regeln wie „jeden Tag, jeden Montag, am 1. des Monats" in einer einzigen Zeile definierst.
- Kalenderbasiert: Du gibst eine absolute Uhrzeit/einen Tag an, statt selbst die verstrichene Zeit zu berechnen.
- Zeitzonen-Unterstützung: Egal in welcher Region dein Server läuft, du kannst die Nachricht zur korrekten lokalen Zeit auslösen.
- Leichtgewichtig: Es hängt weder von einem externen Dienst noch von einer Datenbank ab; es läuft, solange der Bot-Prozess aktiv ist.
Wenn dein Bot nicht rund um die Uhr online ist, wird jede Nachricht, deren Planung auslöst, während der Bot offline ist, übersprungen. Deshalb ist es für geplante Aufgaben unerlässlich, den Bot auf einem VPS mit PM2 oder auf einem unterbrechungsfreien Hosting laufen zu lassen.
Installation und Cron-Syntax
Installieren wir zunächst die Pakete:
npm install discord.js node-cron
node-cron verwendet die Standard-Cron-Syntax; zusätzlich kannst du vorne ein optionales Sekunden-Feld einfügen. Die Reihenfolge der Felder ist:
# ┌──────────── Sekunde (0-59, optional)
# │ ┌────────── Minute (0-59)
# │ │ ┌──────── Stunde (0-23)
# │ │ │ ┌────── Tag des Monats (1-31)
# │ │ │ │ ┌──── Monat (1-12)
# │ │ │ │ │ ┌── Wochentag (0-7, 0 und 7 = Sonntag)
# │ │ │ │ │ │
# * * * * * *
Ein paar praktische Beispiele:
0 9 * * *→ jeden Tag um 09:0030 18 * * 5→ jeden Freitag um 18:300 */6 * * *→ alle 6 Stunden (00:00, 06:00, 12:00, 18:00)0 0 1 * *→ um Mitternacht am 1. jedes Monats
Du kannst prüfen, ob dein Ausdruck gültig ist, mit cron.validate('0 9 * * *'); es gibt true/false zurück.
Eine Nachricht zu einer bestimmten Uhrzeit mit discord.js senden
Die Kernlogik besteht aus zwei Teilen: den Bot bereitmachen und die Cron-Aufgaben im ClientReady-Event starten. Wenn eine Aufgabe auslöst, holen wir den Zielkanal und senden die Nachricht mit send():
const { Client, GatewayIntentBits } = require('discord.js');
const cron = require('node-cron');
const client = new Client({
intents: [GatewayIntentBits.Guilds],
});
const CHANNEL_ID = '123456789012345678';
client.once('clientReady', () => {
console.log(`Angemeldet als ${client.user.tag}`);
// Tagliche Nachricht um 09:00
cron.schedule('0 9 * * *', async () => {
try {
const channel = await client.channels.fetch(CHANNEL_ID);
if (channel?.isTextBased()) {
await channel.send('Guten Morgen! Hab einen tollen Tag. ☀️');
}
} catch (err) {
console.error('Geplante Nachricht konnte nicht gesendet werden:', err);
}
}, {
timezone: 'Europe/Berlin',
});
});
client.login(process.env.DISCORD_TOKEN);
Wichtige Punkte:
client.channels.fetch()ruft den Kanal aus der API ab, selbst wenn er nicht im Cache liegt, was es zuverlässiger macht alscache.get().- Die
isTextBased()-Prüfung bestätigt, dass der Kanal tatsächlich Nachrichten empfangen kann, und verhindert Typfehler. - Ein
try/catchist Pflicht: Wenn der Kanal gelöscht wurde oder der Bot seine Berechtigung verloren hat, darf der Fehler den Bot nicht zum Absturz bringen.
Um nur Nachrichten zu senden, reicht GatewayIntentBits.Guilds; du brauchst keine privilegierten Intents wie MessageContent.
Zeitzonen: der häufigste Fehler
Was die Leute bei geplanten Nachrichten am meisten überrascht, ist, dass die Nachricht „zur falschen Zeit" ankommt. Die Ursache ist fast immer die Zeitzone. Wenn du die Option timezone nicht übergibst, verwendet node-cron die lokale Zeit des Servers. Ein VPS läuft meist in UTC, sodass 0 9 * * * in Deutschland auf 10:00 oder 11:00 fallen kann.
Die Lösung ist, die Option timezone immer explizit zu setzen. Der Wert muss ein IANA-Zeitzonenname sein, etwa Europe/Berlin, Europe/Paris oder America/New_York. Ein angenehmer Nebeneffekt: Es verarbeitet Sommer-/Winterzeit-Umstellungen automatisch; du änderst nichts, node-cron löst zum korrekten lokalen Moment aus.
Nachrichten persistent speichern und dynamische Planung
Das obige Beispiel codiert die Nachrichten fest im Quelltext. In einem echten Bot möchtest du, dass Administratoren neue geplante Nachrichten über einen Slash-Befehl hinzufügen. In diesem Fall musst du die Planungen in einer Datenbank (SQLite, MongoDB usw.) speichern, denn die Aufgaben im Arbeitsspeicher gehen bei jedem Neustart des Bots verloren. Der allgemeine Ablauf sieht so aus:
- Ein Administrator gibt über einen Befehl einen Kanal, eine Zeit (Cron-Ausdruck) und einen Text ein; der Datensatz wird in die Datenbank geschrieben.
- Beim Start des Bots (
clientReady) werden alle Datensätze gelesen und für jeden einecron.schedule-Aufgabe erstellt. - Du bewahrst die Aufgaben in einer
Mapauf, indiziert nach der Datensatz-ID, damit du sie später abbrechen kannst.
Um eine Aufgabe zu stoppen und neu zu starten, verwendest du das von node-cron zurückgegebene Objekt:
const tasks = new Map();
function scheduleMessage(record) {
const task = cron.schedule(record.cronExpr, async () => {
const channel = await client.channels.fetch(record.channelId);
if (channel?.isTextBased()) await channel.send(record.text);
}, { timezone: record.timezone });
tasks.set(record.id, task);
}
// Eine Planung abbrechen:
function cancelMessage(id) {
const task = tasks.get(id);
if (task) {
task.stop();
tasks.delete(id);
}
}
Mit dieser Struktur kannst du eine neue Nachricht über einen „/planen"-Befehl hinzufügen und eine bestehende Planung über „/abbrechen" stoppen. Für einmalige Erinnerungen passt die Bibliothek node-schedule, die zu einem einzelnen Datum/Zeitpunkt auslöst, besser als cron; für wiederkehrende Nachrichten ist node-cron ideal.
Häufige Fragen
Was passiert mit Nachrichten, die verpasst werden, während der Bot offline ist?
node-cron löst nur aus, während der Prozess läuft; eine Planung, die fällig wird, während der Bot offline ist, wird übersprungen und nicht nachgeholt. Deshalb musst du den Bot für geplante Aufgaben mit PM2 oder systemd rund um die Uhr online halten. Willst du verpasste Läufe ausgleichen, musst du zusätzliche Logik schreiben, die die letzte Ausführungszeit in der Datenbank speichert und beim Start prüft.
Was ist der Unterschied zwischen node-cron und node-schedule?
node-cron ist für wiederkehrende Aufgaben mit Cron-Syntax ausgelegt („jeden Tag um 09:00"). node-schedule kann einmalig zu einem bestimmten Date-Objekt auslösen („einmal am 17. August um 14:30"), was es für einzelne Erinnerungen besser macht. Beide unterstützen Zeitzonen.
Sind sehr häufige Aufgaben wie einmal pro Sekunde sinnvoll?
Sie sind möglich, aber selten nötig. Zu häufiges Senden von Nachrichten stößt an die Rate-Limits von Discord und überflutet den Kanal. Für geplante Nachrichten reicht die Minuten- oder Stundenskala völlig; nutze das Sekundenfeld nur, wenn es einen echten Bedarf gibt.
Brauchst du ein zuverlässiges Planungssystem für deinen Bot? Ich kann eine Infrastruktur für geplante Nachrichten mit korrekten Zeitzonen, Datenbankanbindung und Neustart-Resilienz aufbauen. Kontaktiere mich und lass uns dein Projekt gemeinsam planen.