L'une des fonctionnalités les plus demandées dans un bot de communauté est un système de message programmé Discord : un bot qui publie une annonce quotidienne à heure fixe, envoie un rappel d'événement le week-end ou poste un « bonne nuit » à minuit. Dans ce guide, nous allons construire une base solide qui envoie des messages automatiques à une heure précise avec discord.js et node-cron, en traitant un à un les fuseaux horaires, le stockage persistant et les pièges courants.
L'approche : pourquoi node-cron ?
La première idée qui vient pour publier à heure fixe est setTimeout ou setInterval, mais ils fonctionnent par intervalles relatifs du type « toutes les 24 heures » ; ils ne peuvent pas exprimer une règle basée sur le calendrier comme « chaque jour à 09:00 ». C'est précisément là que node-cron excelle : il exécute la syntaxe cron de Linux à l'intérieur de Node.js, donc vous définissez des règles comme « chaque jour, chaque lundi, le 1er du mois » en une seule ligne.
- Basé sur le calendrier : vous indiquez une heure/un jour absolus au lieu de calculer vous-même le temps écoulé.
- Gestion des fuseaux : quelle que soit la région de votre serveur, vous pouvez déclencher le message à la bonne heure locale.
- Léger : il ne dépend ni d'un service externe ni d'une base de données ; il tourne tant que le processus du bot est actif.
Si votre bot n'est pas en ligne 24h/24, tout message dont la planification se déclenche pendant que le bot est hors ligne est ignoré. C'est pourquoi faire tourner le bot sur un VPS avec PM2, ou sur un hébergement sans interruption, est indispensable pour les tâches programmées.
Installation et syntaxe cron
Installons d'abord les paquets :
npm install discord.js node-cron
node-cron utilise la syntaxe cron standard ; en plus, vous pouvez ajouter en tête un champ secondes optionnel. L'ordre des champs est le suivant :
# ┌──────────── seconde (0-59, optionnel)
# │ ┌────────── minute (0-59)
# │ │ ┌──────── heure (0-23)
# │ │ │ ┌────── jour du mois (1-31)
# │ │ │ │ ┌──── mois (1-12)
# │ │ │ │ │ ┌── jour de la semaine (0-7, 0 et 7 = dimanche)
# │ │ │ │ │ │
# * * * * * *
Quelques exemples pratiques :
0 9 * * *→ chaque jour à 09:0030 18 * * 5→ chaque vendredi à 18:300 */6 * * *→ toutes les 6 heures (00:00, 06:00, 12:00, 18:00)0 0 1 * *→ à minuit le 1er de chaque mois
Vous pouvez vérifier la validité de votre expression avec cron.validate('0 9 * * *') ; elle renvoie true/false.
Envoyer un message à une heure précise avec discord.js
La logique de base comporte deux parties : préparer le bot et démarrer les tâches cron dans l'événement ClientReady. Quand une tâche se déclenche, on récupère le salon cible et on envoie le message avec 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(`Connecte en tant que ${client.user.tag}`);
// Message quotidien a 09:00
cron.schedule('0 9 * * *', async () => {
try {
const channel = await client.channels.fetch(CHANNEL_ID);
if (channel?.isTextBased()) {
await channel.send('Bonjour ! Passez une excellente journee. ☀️');
}
} catch (err) {
console.error('Echec de l envoi du message programme:', err);
}
}, {
timezone: 'Europe/Paris',
});
});
client.login(process.env.DISCORD_TOKEN);
Points clés :
client.channels.fetch()récupère le salon depuis l'API même s'il n'est pas en cache, ce qui le rend plus fiable quecache.get().- Le contrôle
isTextBased()confirme que le salon peut réellement recevoir des messages et évite les erreurs de type. - Un
try/catchest obligatoire : si le salon a été supprimé ou si le bot a perdu sa permission, l'erreur ne doit pas faire planter le bot.
Pour envoyer uniquement des messages, GatewayIntentBits.Guilds suffit ; vous n'avez pas besoin d'intents privilégiés comme MessageContent.
Fuseaux horaires : l'erreur la plus fréquente
Ce qui surprend le plus avec les messages programmés, c'est que le message arrive « à la mauvaise heure ». La cause est presque toujours le fuseau horaire. Si vous ne passez pas l'option timezone, node-cron utilise l'heure locale du serveur. Un VPS tourne généralement en UTC, donc 0 9 * * * peut tomber à 11:00 en France.
La solution est de toujours définir explicitement l'option timezone. La valeur doit être un nom de fuseau IANA, comme Europe/Paris, Europe/Istanbul ou America/New_York. Un effet agréable : elle gère automatiquement les changements d'heure été/hiver ; vous ne modifiez rien, node-cron se déclenche selon le bon moment local.
Persister les messages et planification dynamique
L'exemple ci-dessus code les messages en dur dans la source. Dans un vrai bot, vous voudrez que les administrateurs ajoutent de nouveaux messages programmés via une commande slash. Dans ce cas, vous devez stocker les planifications dans une base de données (SQLite, MongoDB, etc.), car les tâches en mémoire sont perdues à chaque redémarrage du bot. Le flux général ressemble à ceci :
- Un administrateur saisit un salon, une heure (expression cron) et un texte via une commande ; l'enregistrement est écrit en base.
- Au démarrage du bot (
clientReady), tous les enregistrements sont lus et une tâchecron.scheduleest créée pour chacun. - Vous conservez les tâches dans une
Mapindexée par l'identifiant de l'enregistrement afin de pouvoir les annuler plus tard.
Pour arrêter et relancer une tâche, on utilise l'objet renvoyé par node-cron :
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);
}
// Annuler une planification :
function cancelMessage(id) {
const task = tasks.get(id);
if (task) {
task.stop();
tasks.delete(id);
}
}
Avec cette structure, vous pouvez ajouter un nouveau message via une commande « /programmer » et arrêter une planification existante via « /annuler ». Pour des rappels uniques, la bibliothèque node-schedule, qui se déclenche à une seule date/heure, convient mieux que cron ; pour des messages récurrents, node-cron est idéal.
Questions fréquentes
Que deviennent les messages manqués pendant que le bot est hors ligne ?
node-cron ne se déclenche que lorsque le processus tourne ; une planification qui arrive à échéance pendant que le bot est hors ligne est ignorée et non rattrapée. C'est pourquoi il faut maintenir le bot en ligne 24h/24 avec PM2 ou systemd pour les tâches programmées. Pour compenser les exécutions manquées, vous devez écrire une logique supplémentaire qui stocke la dernière heure d'exécution en base et la vérifie au démarrage.
Quelle est la différence entre node-cron et node-schedule ?
node-cron est conçu pour des tâches récurrentes avec la syntaxe cron (« chaque jour à 09:00 »). node-schedule peut se déclencher une seule fois à un objet Date précis (« une fois le 17 août à 14:30 »), ce qui le rend meilleur pour des rappels uniques. Les deux gèrent les fuseaux horaires.
Des tâches très fréquentes comme une fois par seconde ont-elles du sens ?
C'est possible mais rarement nécessaire. Envoyer des messages trop souvent vous fera atteindre les rate limits de Discord et encombrera le salon. Pour des messages programmés, l'échelle de la minute ou de l'heure suffit ; n'utilisez le champ des secondes qu'en cas de réel besoin.
Besoin d'un système de planification fiable pour votre bot ? Je peux mettre en place une infrastructure de messages programmés avec des fuseaux corrects, un stockage en base et une résistance aux redémarrages. Contactez-moi et planifions votre projet ensemble.