Créer un Bot Discord avec Claude Code : guide pratique discord.js
Créez un Bot Discord de support avec Claude Code: slash commands, permissions, env vars et checklist de déploiement.
Un Bot Discord doit être un flux d’exploitation, pas seulement une démo
Un Bot Discord est une application qui rejoint un serveur Discord et réagit aux actions des utilisateurs. Dit simplement, c’est un point d’accueil automatique dans une communauté. Il peut recevoir une demande de support, répondre à une FAQ courte, prévenir des modérateurs, préparer une note de handoff ou envoyer une alerte dans un canal interne avant que l’information se perde.
C’est beaucoup plus utile qu’un bot qui sait seulement discuter. Une communauté saine a besoin de chemins fiables pour “je suis bloqué”, “où est le guide d’installation”, “qui prend ce sujet” et “faut-il escalader”. Si tout reste dans un salon général, les messages disparaissent sous la conversation. Avec des application commands, le flux devient visible, répétable et plus facile à relire.
Claude Code est intéressant ici quand on lui demande plus qu’un extrait client.login(). Un bot prêt pour la production demande l’enregistrement des commandes, des variables d’environnement, des limites de permissions, des réponses d’interaction, une gestion d’erreurs, la protection du token et une petite procédure de déploiement. Une demande vague comme “fais un Bot Discord” produit souvent une démo qui marche seulement dans le cas idéal.
Ce guide construit un bot de support avec discord.js. Il implémente /support, /faq et /handoff, utilise des slash commands, évite l’intent de contenu des messages, limite les mentions dangereuses et commence avec des commandes de serveur de test. L’exemple suppose Node.js 22.12.0 ou plus récent, conformément à la documentation actuelle de discord.js.
flowchart LR
A["User runs /support"] --> B["Discord interaction"]
B --> C["discord.js bot"]
C --> D["Ephemeral user reply"]
C --> E["Support channel message"]
E --> F["Moderator handoff"]
Le premier objectif n’est pas de créer un conseiller IA impressionnant. L’objectif est un chemin d’accueil fiable. Base de données, file d’attente, résumé LLM, CRM ou vérification de paiement peuvent arriver plus tard. D’abord, il faut que la commande arrive, que l’utilisateur reçoive une réponse privée et que l’équipe voie une note exploitable.
Application commands et interactions en langage simple
Les Discord application commands sont des commandes natives visibles dans le client Discord. La forme la plus connue est le slash command, comme /support. Elles conviennent mieux au support que les anciennes commandes à préfixe comme !help, car Discord affiche le nom, la description, les options, les choix et les règles de permission avant l’envoi.
Les interactions sont les événements reçus par votre application quand un utilisateur lance une commande, clique sur un bouton, utilise un menu ou soumet une modal. Avec discord.js via le Gateway, on les traite généralement dans Events.InteractionCreate. Discord peut aussi envoyer les interactions vers un endpoint HTTP, mais pour une petite équipe, un Bot Gateway est plus simple à lancer en local et à déboguer.
La source de vérité doit rester officielle. Les types de commandes, les règles de nommage, la différence entre guild commands et global commands et l’enregistrement sont documentés dans Discord Application Commands. Les réponses initiales, followups et tokens d’interaction sont expliqués dans Receiving and Responding to Interactions. Pour l’API de la bibliothèque et les exigences Node, utilisez discord.js documentation et le discord.js guide.
Permissions, variables d’environnement et architecture minimale
Dans le Developer Portal, créez une Discord application, ajoutez un utilisateur Bot, puis générez une URL d’invitation avec les scopes bot et applications.commands. Ne commencez pas avec la permission administrator. Ce bot doit voir le canal de support et y envoyer des messages. La commande /handoff doit être réservée aux personnes ayant un niveau de modération, par exemple Manage Messages.
| Élément | Valeur | Note de production |
|---|---|---|
| Node.js | 22.12.0 ou plus | Exigence actuelle de discord.js |
| OAuth2 scopes | bot, applications.commands | Nécessaires pour le bot et les slash commands |
| Bot permissions | View Channels, Send Messages | Ajouter plus seulement après revue |
DISCORD_TOKEN | Bot token | Ne jamais committer, capturer ou logger |
DISCORD_CLIENT_ID | Application ID | Utilisé pour enregistrer les commandes |
DISCORD_GUILD_ID | ID du serveur de test | Utilisé pour les guild commands |
SUPPORT_CHANNEL_ID | Canal interne de support | Le bot doit pouvoir y écrire |
Un bon prompt pour Claude Code serait : “Crée un bot de support Node.js 22 avec discord.js. Il doit avoir /support, /faq et /handoff, utiliser .env, enregistrer des guild commands en développement, limiter les permissions, répondre en ephemeral reply et générer une checklist de déploiement.” Cette précision évite le squelette de chatbot trop générique.
Pour renforcer la méthode, lisez aussi gestion des variables d’environnement, patterns de gestion d’erreurs et checklist de code review. Le bot est petit, mais les habitudes sont celles d’un service de production.
Starter discord.js exécutable
L’exemple utilise JavaScript avec ES modules afin de rester facile à copier sans configuration TypeScript. Si DISCORD_GUILD_ID est présent, les commandes sont enregistrées dans le serveur de test. S’il est retiré, elles deviennent globales. Gardez DEPLOY_COMMANDS=true pendant le setup local, puis faites de l’enregistrement des commandes une étape contrôlée en production.
mkdir discord-support-bot
cd discord-support-bot
npm init -y
npm install discord.js dotenv
mkdir src
Ajoutez type et start dans package.json.
{
"type": "module",
"scripts": {
"start": "node src/bot.js"
},
"dependencies": {
"discord.js": "latest",
"dotenv": "latest"
}
}
Créez .env.
DISCORD_TOKEN=replace_with_bot_token
DISCORD_CLIENT_ID=replace_with_application_id
DISCORD_GUILD_ID=replace_with_test_guild_id
SUPPORT_CHANNEL_ID=replace_with_support_channel_id
DEPLOY_COMMANDS=true
Créez src/bot.js.
import "dotenv/config";
import {
Client,
Events,
GatewayIntentBits,
MessageFlags,
PermissionFlagsBits,
REST,
Routes,
SlashCommandBuilder,
} from "discord.js";
const token = process.env.DISCORD_TOKEN;
const clientId = process.env.DISCORD_CLIENT_ID;
const guildId = process.env.DISCORD_GUILD_ID;
const supportChannelId = process.env.SUPPORT_CHANNEL_ID;
for (const [name, value] of Object.entries({ token, clientId, supportChannelId })) {
if (!value) throw new Error(`${name} is required.`);
}
const commands = [
new SlashCommandBuilder()
.setName("support")
.setDescription("Send a support request to the team")
.addStringOption((option) =>
option
.setName("summary")
.setDescription("What happened?")
.setMaxLength(900)
.setRequired(true),
)
.addStringOption((option) =>
option
.setName("severity")
.setDescription("How urgent is it?")
.setRequired(true)
.addChoices(
{ name: "low", value: "low" },
{ name: "normal", value: "normal" },
{ name: "high", value: "high" },
),
)
.addStringOption((option) =>
option
.setName("context")
.setDescription("Steps, links, or error messages")
.setMaxLength(1500),
),
new SlashCommandBuilder()
.setName("faq")
.setDescription("Show a short answer for a common topic")
.addStringOption((option) =>
option
.setName("topic")
.setDescription("FAQ topic")
.setRequired(true)
.addChoices(
{ name: "setup", value: "setup" },
{ name: "permissions", value: "permissions" },
{ name: "rollout", value: "rollout" },
),
),
new SlashCommandBuilder()
.setName("handoff")
.setDescription("Create a moderator handoff note")
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.addUserOption((option) =>
option.setName("target").setDescription("User to hand off").setRequired(true),
)
.addStringOption((option) =>
option
.setName("note")
.setDescription("What should the next moderator know?")
.setMaxLength(1500)
.setRequired(true),
),
].map((command) => command.toJSON());
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, (readyClient) => {
console.log(`Logged in as ${readyClient.user.tag}`);
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
try {
if (!interaction.inGuild()) {
await interaction.reply({
content: "Please use this command inside the server.",
flags: MessageFlags.Ephemeral,
});
return;
}
if (interaction.commandName === "support") await handleSupport(interaction);
else if (interaction.commandName === "faq") await handleFaq(interaction);
else if (interaction.commandName === "handoff") await handleHandoff(interaction);
else await safeReply(interaction, "Unknown command.");
} catch (error) {
console.error("Interaction failed:", error);
await safeReply(interaction, "Something went wrong. Please contact a moderator.");
}
});
async function handleSupport(interaction) {
const summary = interaction.options.getString("summary", true);
const severity = interaction.options.getString("severity", true);
const context = interaction.options.getString("context") ?? "No extra context.";
const channel = await fetchSupportChannel();
await channel.send({
content: [
"**New support request**",
`Reporter: ${interaction.user.tag} (${interaction.user.id})`,
`Severity: ${severity}`,
`Channel: <#${interaction.channelId}>`,
`Summary: ${neutralizeMentions(summary)}`,
`Context: ${neutralizeMentions(context)}`,
].join("\n"),
allowedMentions: { parse: [] },
});
await interaction.reply({
content: "Thanks. Your request was sent to the support team.",
flags: MessageFlags.Ephemeral,
});
}
async function handleFaq(interaction) {
const topic = interaction.options.getString("topic", true);
const answers = {
setup: "Install Node.js 22.12+, invite the bot with bot and applications.commands scopes, then run npm start.",
permissions: "Start with View Channels and Send Messages. Reserve Manage Messages for moderator-only commands.",
rollout: "Use guild commands for testing. Promote to global commands only after rollback and logging are checked.",
};
await interaction.reply({
content: answers[topic],
flags: MessageFlags.Ephemeral,
});
}
async function handleHandoff(interaction) {
if (!interaction.memberPermissions?.has(PermissionFlagsBits.ManageMessages)) {
await interaction.reply({
content: "You need Manage Messages permission to use this command.",
flags: MessageFlags.Ephemeral,
});
return;
}
const target = interaction.options.getUser("target", true);
const note = interaction.options.getString("note", true);
const channel = await fetchSupportChannel();
await channel.send({
content: [
"**Moderator handoff**",
`Target: ${target.tag} (${target.id})`,
`From: ${interaction.user.tag} (${interaction.user.id})`,
`Note: ${neutralizeMentions(note)}`,
].join("\n"),
allowedMentions: { parse: [] },
});
await interaction.reply({
content: "Handoff note created.",
flags: MessageFlags.Ephemeral,
});
}
async function fetchSupportChannel() {
const channel = await client.channels.fetch(supportChannelId);
if (!channel || !channel.isTextBased() || typeof channel.send !== "function") {
throw new Error("SUPPORT_CHANNEL_ID must be a text channel the bot can send to.");
}
return channel;
}
function neutralizeMentions(value) {
return value
.replaceAll("@everyone", "@ everyone")
.replaceAll("@here", "@ here")
.replace(/<@!?(\d+)>/g, "user:$1")
.replace(/<@&(\d+)>/g, "role:$1");
}
async function safeReply(interaction, content) {
const payload = { content, flags: MessageFlags.Ephemeral };
if (interaction.replied || interaction.deferred) await interaction.followUp(payload);
else await interaction.reply(payload);
}
async function deployCommands() {
const rest = new REST({ version: "10" }).setToken(token);
const route = guildId
? Routes.applicationGuildCommands(clientId, guildId)
: Routes.applicationCommands(clientId);
await rest.put(route, { body: commands });
console.log(guildId ? "Guild commands deployed." : "Global commands deployed.");
}
if (process.env.DEPLOY_COMMANDS === "true") {
await deployCommands();
}
await client.login(token);
En local, lancez node --version et vérifiez 22.12.0 ou plus récent, puis npm start. Testez d’abord /support : l’utilisateur doit recevoir une réponse privée et le canal interne doit recevoir un message structuré. Testez ensuite /faq. Enfin, essayez /handoff avec un compte modérateur et un compte normal pour valider la frontière de permission.
Use case : trois flux qui justifient le bot
Le premier Use case est l’accueil du support. Une bonne commande n’a pas besoin de vingt champs. Avec summary, severity et context, on peut déjà faire du triage, c’est-à-dire choisir si le sujet doit être répondu, reproduit, assigné ou escaladé. Sur un petit serveur de test, ce format a réduit les allers-retours, car la première note contenait déjà l’urgence, le symptôme et une piste de reproduction.
Le deuxième Use case est le routage FAQ. Le bot ne doit pas coller une page entière de documentation dans le chat. Une réponse courte avec le bon lien est plus utile. Une question d’installation peut aller vers le guide de démarrage Claude Code, une question CLI vers développement d’outils CLI, et les règles d’équipe vers modèles CLAUDE.md. Le bot devient une navigation, pas une deuxième documentation.
Le troisième Use case est le handoff entre modérateurs. Dans une communauté payante, une cohorte de formation, un serveur de jeu ou un support produit, les personnes changent de tour. “Quelqu’un peut regarder ?” ne suffit pas. /handoff indique la personne concernée, l’auteur de la note et ce que le prochain modérateur doit savoir.
Un quatrième scénario est le support de formation. Pendant un atelier Claude Code, plusieurs participants rencontrent les mêmes erreurs de version Node, de variables d’environnement ou de commande. Si le bot demande d’abord la version, la commande exécutée et les dernières lignes de log, l’instructeur commence par diagnostiquer au lieu de demander les bases. Pour l’intégrer dans une équipe, la suite naturelle est formation et conseil.
Pitfall : erreurs de production à éliminer tôt
Le premier Pitfall est la fuite du token du bot. S’il apparaît dans Git, une capture, des logs CI ou une documentation, considérez-le compromis. Faites une rotation dans le Developer Portal et retirez l’ancienne valeur. Quand vous utilisez Claude Code, demandez explicitement un .env.example avec des placeholders et aucune valeur réelle.
Le deuxième piège est l’abus de global commands pendant le développement. Les guild commands sont rapides et limitées au serveur de test. Les global commands sont la surface de production. Passez en global seulement après validation des noms, descriptions, permissions et étapes de rollback. Évitez aussi de réenregistrer les commandes à chaque redémarrage de production.
Le troisième piège est une branche d’interaction sans réponse. Pour l’utilisateur, cela ressemble à une commande cassée. Chaque branche doit reply, defer ou follow up, y compris les erreurs et commandes inconnues. Si vous ajoutez un appel API externe ou un résumé LLM, faites defer d’abord, puis envoyez le résultat.
Le quatrième piège est l’abus de mentions. Si vous transférez le texte utilisateur tel quel vers un canal interne, @everyone, @here, mentions utilisateur et mentions de rôle peuvent déclencher un incident de notifications. L’exemple utilise allowedMentions: { parse: [] } et une normalisation du texte. Gardez les deux protections.
Le cinquième piège est de donner administrator au bot par confort. Cela masque les décisions de permission et augmente les dégâts si le token est volé. Commencez avec View Channels et Send Messages, puis ajoutez uniquement ce que vous pouvez justifier en revue.
Checklist sécurité et déploiement
- Une personne peut faire la rotation du token dans le Developer Portal
.envest exclu de Git et.env.examplene contient que des placeholders- L’URL d’invitation commence avec
botetapplications.commands - Les permissions du bot commencent avec View Channels et Send Messages
/handoffest réservé aux modérateurs- Les guild commands sont testées avant les global commands
- Chaque branche d’interaction reply, defer ou follow up
- Les mentions issues du texte utilisateur sont neutralisées
- Les logs n’incluent pas token, paiement, email ou données privées de support
- L’hébergement utilise Node.js 22.12.0 ou plus récent
- Les étapes de redémarrage et de rollback sont écrites
Un petit bot peut tourner sur Railway, Render, Fly.io, un VPS ou un serveur interne. Le choix compte moins que trois points : les secrets sont séparés du code, les logs sont visibles et le redémarrage est documenté. Demandez à Claude Code de rédiger le README avec ces sections.
Pour des modèles réutilisables, consultez les produits ClaudeCodeLab. Si votre équipe veut structurer support Discord, adoption Claude Code, permissions, revue et formation, commencez par formation et conseil. Pour maintenir le bot comme une vraie fonctionnalité, reliez aussi le travail à l’automatisation de code review.
Résultat après test
Après avoir lancé ce starter sur un petit serveur, le gain principal n’était pas l’intelligence du bot. C’était la forme de la demande. Les messages libres commencent souvent par “ça ne marche pas”. /support force summary, severity et context, donc la première réponse du modérateur ressemble davantage à un diagnostic qu’à une demande d’informations. Les zones dangereuses étaient le token, les global commands, les mentions et les permissions. Claude Code a été le plus utile quand je lui ai demandé le code, mais aussi .env.example, le tableau de permissions, les réponses d’erreur et la checklist de déploiement.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.