aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Bots Discord

Bot Discord en TypeScript : une configuration type-safe

Créer un projet bot Discord TypeScript porte ses fruits dès qu'il commence à grandir : votre éditeur connaît l'autocomplétion, il détecte un argument du mauvais type avant même que vous n'exécutiez le code, et lors d'un gros refactor il vous dit exactement quels fichiers cassent dès la compilation. Comme discord.js v14 est lui-même entièrement écrit en TypeScript, utiliser TS vous donne gratuitement toute l'information de typage de la bibliothèque. Dans ce guide, nous allons construire pas à pas un projet discord.js type-safe qui compile et tourne.

Pourquoi TypeScript ?

Un bot écrit en JavaScript pur ne pose aucun problème tant qu'il est petit, mais une fois passé 20-30 commandes, quelques events et une couche base de données, les bugs silencieux s'accumulent. TypeScript en révèle la plupart avant même d'enregistrer :

  • Autocomplétion : tapez interaction. et l'éditeur liste toutes les méthodes et propriétés valides — fini d'ouvrir sans cesse la documentation.
  • Détection précoce des erreurs : accéder à un champ inexistant ou passer le mauvais type à une fonction échoue à la compilation, pas devant un utilisateur.
  • Refactoring sûr : modifiez une interface de commande et chaque fichier qui l'utilise est signalé par le compilateur.
  • Code auto-documenté : les types servent aussi de documentation vivante ; vos coéquipiers peuvent lire ce que chaque élément attend.

Initialiser le projet

On crée d'abord un dossier vide et on installe les paquets nécessaires. discord.js est une dépendance d'exécution, tandis que TypeScript et les paquets de types ne servent qu'au développement.

npm init -y
npm install discord.js
npm install -D typescript @types/node tsx

Ici tsx est un outil rapide qui permet d'exécuter directement des fichiers TypeScript sans étape de build distincte ; en développement il est plus pratique que ts-node. Pour la production, nous ferons un vrai build avec tsc.

Configurer tsconfig.json

Le cœur de la sûreté de typage est le fichier tsconfig.json. Un point de départ solide et strict pour un bot Node.js moderne ressemble à ceci :

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

La ligne la plus critique est "strict": true ; elle active les vérifications de null, l'interdiction du any implicite et bien plus, d'un seul coup. C'est avec ce drapeau que vous tirez toute la valeur de TypeScript — le désactiver revient à conduire avec le frein à main serré.

La première connexion et un Client typé

Que le point d'entrée du bot soit src/index.ts. La classe Client de discord.js est déjà entièrement typée, donc si vous essayez de passer une valeur invalide dans le tableau intents, l'éditeur vous avertit aussitôt.

// src/index.ts
import { Client, GatewayIntentBits, Events } from 'discord.js';

const client = new Client({
  intents: [GatewayIntentBits.Guilds],
});

client.once(Events.ClientReady, (c) => {
  console.log(`Connecté en tant que : ${c.user.tag}`);
});

client.login(process.env.DISCORD_TOKEN);

Ici le type du paramètre c est automatiquement inféré comme Client<true>, donc TypeScript garantit que c.user n'est pas null et vous pouvez accéder à .tag en toute sécurité.

Définir une interface pour les commandes

Chaque commande Slash doit suivre la même forme pour que le handler puisse les traiter uniformément. On verrouille cela dans un contrat avec une interface :

// src/types.ts
import {
  ChatInputCommandInteraction,
  SlashCommandBuilder,
  SlashCommandOptionsOnlyBuilder,
} from 'discord.js';

export interface Command {
  data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder;
  execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
}

Désormais chaque fichier de commande doit implémenter ce type Command. Si vous oubliez d'écrire la fonction execute dans une commande ou utilisez le mauvais type de paramètre, vous verrez le soulignement rouge dès l'enregistrement du fichier.

// src/commands/ping.ts
import { SlashCommandBuilder } from 'discord.js';
import type { Command } from '../types.js';

export const ping: Command = {
  data: new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Mesure la latence du bot'),

  async execute(interaction) {
    await interaction.reply(`Pong! ${interaction.client.ws.ping}ms`);
  },
};

Étendre le Client : le declaration merging

Nous voulons stocker les commandes dans une collection comme client.commands, mais le type Client de discord.js n'a pas une telle propriété. Le declaration merging de TypeScript résout exactement ce problème : on étend la définition de type de la bibliothèque avec notre propre champ.

// src/types.ts (suite)
import { Collection } from 'discord.js';

declare module 'discord.js' {
  interface Client {
    commands: Collection<string, Command>;
  }
}

Grâce à ce bloc, quand vous écrivez client.commands.get('ping') l'éditeur connaît le bon type ; vous n'avez pas besoin d'utiliser un faux any ni de faire un cast à chaque accès.

Charger et exécuter les commandes

Au démarrage du bot, on remplit la collection avec les fichiers de commandes, puis on route chaque interaction entrante vers la bonne commande. Capturer les erreurs de manière centralisée dans interactionCreate est l'approche la plus saine.

// src/index.ts (ajouts)
import { Collection } from 'discord.js';
import { ping } from './commands/ping.js';

client.commands = new Collection();
client.commands.set(ping.data.name, ping);

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  const command = client.commands.get(interaction.commandName);
  if (!command) return;

  try {
    await command.execute(interaction);
  } catch (err) {
    console.error(err);
    await interaction.reply({
      content: 'Une erreur est survenue lors de l\'exécution de cette commande.',
      ephemeral: true,
    });
  }
});

La vérification interaction.isChatInputCommand() est un type guard : après cette ligne, TypeScript restreint interaction à ChatInputCommandInteraction, vous pouvez donc accéder en toute sécurité à des champs comme commandName et reply.

Scripts de développement et de production

La dernière étape consiste à ajouter deux scripts à package.json : exécution instantanée avec tsx pour le développement, et compilation vers le dossier dist/ avec tsc pour la production.

{
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Pendant le développement, npm run dev surveille les changements de fichiers et redémarre le bot automatiquement. Pour le déploiement sur un serveur, vous produisez du JavaScript pur avec npm run build et l'exécutez avec npm start — aucun TypeScript n'est nécessaire à l'exécution en production.

Questions fréquentes

Dois-je utiliser ESM ou CommonJS ?

L'ESM avec "type": "module" est recommandé pour les nouveaux projets ; discord.js v14 prend en charge les deux. En ESM, n'oubliez pas que vous devez écrire l'extension .js sur les imports de fichiers locaux (par exemple './types.js') — c'est indispensable pour que la sortie compilée se résolve correctement.

Pourquoi tsx plutôt que ts-node ?

tsx est basé sur esbuild, il démarre donc beaucoup plus vite et ne demande quasiment aucune configuration pour l'ESM. Il ne vérifie pas les types, il se contente d'exécuter ; c'est pourquoi utiliser tsx pour la vitesse en développement et une vraie vérification de types avec tsc au build est la combinaison idéale.

Puis-je exécuter TypeScript directement en production ?

Techniquement possible avec tsx, mais déconseillé. Précompiler avec tsc en production accélère le démarrage et détecte les erreurs de type au build, empêchant du code cassé d'atteindre votre serveur.

Vous voulez poser votre bot sur une base type-safe et maintenable ? D'une configuration TypeScript partie de zéro à l'architecture du command handler et au déploiement, nous pouvons mettre votre projet sur des bases solides ensemble. Contactez-moi et planifions cela selon vos besoins.

Bu kategorideki tüm yazılar →

Devamı için