Use Cases (Mis à jour: 03/06/2026)

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.

Créer un Bot Discord avec Claude Code : guide pratique discord.js

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émentValeurNote de production
Node.js22.12.0 ou plusExigence actuelle de discord.js
OAuth2 scopesbot, applications.commandsNécessaires pour le bot et les slash commands
Bot permissionsView Channels, Send MessagesAjouter plus seulement après revue
DISCORD_TOKENBot tokenNe jamais committer, capturer ou logger
DISCORD_CLIENT_IDApplication IDUtilisé pour enregistrer les commandes
DISCORD_GUILD_IDID du serveur de testUtilisé pour les guild commands
SUPPORT_CHANNEL_IDCanal interne de supportLe 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
  • .env est exclu de Git et .env.example ne contient que des placeholders
  • L’URL d’invitation commence avec bot et applications.commands
  • Les permissions du bot commencent avec View Channels et Send Messages
  • /handoff est 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.

#Claude Code #Discord Bot #discord.js #chatbot #communauté
Gratuit

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.

Masa

À propos de l'auteur

Masa

Ingénieur spécialisé dans les workflows pratiques avec Claude Code.