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

Bot de niveau Discord : système d'XP et récompenses

Si vous voulez une communauté active, récompenser les membres fonctionne : un bot de niveau Discord attribue de l'XP aux utilisateurs pour chaque message, les fait monter de niveau à certains seuils et, si vous le souhaitez, distribue des rôles automatiquement. Des bots prêts à l'emploi comme MEE6 ou Arcane le font, mais en construisant la logique vous-même il n'y a aucune limite : vous contrôlez entièrement la formule d'XP, les règles anti-spam, le design de la carte de rang et les rôles de récompense. Dans cet article, nous allons créer un système d'XP fonctionnel de zéro avec discord.js v14.

Comment ça marche : la logique d'XP et de niveau

L'idée de base est simple : le bot écoute chaque message du serveur et ajoute une petite quantité d'XP au membre qui l'a envoyé. Lorsque l'XP atteint un seuil, le membre passe au niveau suivant. Deux points méritent attention :

  • Anti-spam (cooldown) : Pour qu'un membre ne puisse pas accumuler de l'XP en inondant le salon de messages, on fixe un temps d'attente par utilisateur (par ex. 60 secondes). Les messages envoyés avant la fin de ce délai ne rapportent aucune XP.
  • Courbe de niveau : Pour rendre chaque niveau un peu plus difficile, on calcule le seuil d'XP avec une formule croissante. Dans la formule populaire de MEE6, l'XP nécessaire pour passer du niveau x au niveau x+1 est 5 * x² + 50 * x + 100. Ainsi, le niveau 0→1 demande 100 XP, 1→2 demande 155 XP ; le nombre augmente à chaque étape.

Configuration du projet et intents requis

Commençons par créer un dossier de projet et installer les dépendances. Pour stocker l'XP de façon persistante, nous utiliserons better-sqlite3, léger et rapide :

npm init -y
npm install discord.js better-sqlite3 dotenv

Pour lire le contenu des messages, le bot a besoin du privilège Message Content Intent. Activez-le à la fois dans le Portail développeur Discord → Bot → Privileged Gateway Intents, et déclarez les intents nécessaires dans le code :

import { Client, GatewayIntentBits, Events } from 'discord.js';
import 'dotenv/config';

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

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

client.login(process.env.TOKEN);

Ne codez jamais le token du bot en dur ; placez-le dans un fichier .env sous la forme TOKEN=... et ajoutez .env au .gitignore.

Base de données : stocker l'XP durablement

Pour que la progression ne soit pas réinitialisée à chaque redémarrage du bot, il faut écrire l'XP sur le disque. Une table simple avec une ligne par serveur (guild) et utilisateur suffit :

import Database from 'better-sqlite3';
const db = new Database('levels.db');

db.exec(`
  CREATE TABLE IF NOT EXISTS levels (
    guild_id TEXT NOT NULL,
    user_id  TEXT NOT NULL,
    xp       INTEGER NOT NULL DEFAULT 0,
    level    INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (guild_id, user_id)
  );
`);

// Récupérer l'enregistrement d'un utilisateur (le créer s'il manque)
function getUser(guildId, userId) {
  let row = db.prepare(
    'SELECT * FROM levels WHERE guild_id = ? AND user_id = ?'
  ).get(guildId, userId);
  if (!row) {
    db.prepare(
      'INSERT INTO levels (guild_id, user_id) VALUES (?, ?)'
    ).run(guildId, userId);
    row = { guild_id: guildId, user_id: userId, xp: 0, level: 0 };
  }
  return row;
}

Stocker l'ID du serveur compte aussi : si le même bot tourne sur plusieurs serveurs, les niveaux de chacun restent séparés.

Ajouter de l'XP par message

Voici le cœur du système : l'événement messageCreate. On s'assure que les bots et les messages système ne gagnent pas d'XP, on applique le cooldown et on vérifie les montées de niveau :

const cooldowns = new Map(); // userId -> dernier instant d'XP
const COOLDOWN_MS = 60_000;  // 60 secondes

// XP nécessaire pour passer du niveau x à x+1
const xpForNext = (level) => 5 * level ** 2 + 50 * level + 100;

client.on(Events.MessageCreate, async (message) => {
  if (message.author.bot || !message.guild) return;

  const key = `${message.guild.id}-${message.author.id}`;
  const now = Date.now();
  if (cooldowns.has(key) && now - cooldowns.get(key) < COOLDOWN_MS) return;
  cooldowns.set(key, now);

  const user = getUser(message.guild.id, message.author.id);
  const gain = 15 + Math.floor(Math.random() * 11); // 15–25 XP
  let { xp, level } = user;
  xp += gain;

  // Monter de niveau tant qu'assez d'XP est accumulée
  while (xp >= xpForNext(level)) {
    xp -= xpForNext(level);
    level += 1;
    onLevelUp(message, level);
  }

  db.prepare(
    'UPDATE levels SET xp = ?, level = ? WHERE guild_id = ? AND user_id = ?'
  ).run(xp, level, message.guild.id, message.author.id);
});

Accorder 15 à 25 XP aléatoires par message est l'approche classique qu'utilise aussi MEE6 ; un peu d'aléatoire au lieu d'une valeur fixe rend le système plus difficile à exploiter.

Récompenses de rôle et annonce de montée de niveau

Lors d'une montée de niveau, on veut deux choses : poster un message de célébration dans le salon et attribuer le rôle lié à ce seuil. Vous pouvez conserver les seuils de rôle dans un simple objet :

const ROLE_REWARDS = {
  5:  'ID_ROLE_APPRENTI',
  10: 'ID_ROLE_MAITRE',
  20: 'ID_ROLE_LEGENDE',
};

async function onLevelUp(message, level) {
  await message.channel.send(
    `🎉 ${message.author}, félicitations ! Tu es désormais **niveau ${level}**.`
  );

  const roleId = ROLE_REWARDS[level];
  if (roleId) {
    const role = message.guild.roles.cache.get(roleId);
    if (role) {
      await message.member.roles.add(role).catch(console.error);
    }
  }
}

Pour que les rôles fonctionnent, le rôle du bot doit se situer au-dessus des rôles qu'il attribue (Paramètres du serveur → Rôles), et le bot doit disposer de la permission Manage Roles (Gérer les rôles). Sinon, roles.add renverra une erreur de permission.

La carte de rang : une commande rank

Les utilisateurs veulent voir leur niveau. L'approche la plus simple est de renvoyer une carte textuelle via une commande slash. Pour une carte visuelle, vous pouvez dessiner un PNG avec @napi-rs/canvas et l'envoyer avec AttachmentBuilder ; mais un embed suffit pour démarrer :

// commande /rank (doit être enregistrée au préalable)
if (interaction.commandName === 'rank') {
  const u = getUser(interaction.guild.id, interaction.user.id);
  const need = 5 * u.level ** 2 + 50 * u.level + 100;
  await interaction.reply(
    `**${interaction.user.username}** — Niveau ${u.level} | ` +
    `${u.xp} / ${need} XP`
  );
}

Vous devez enregistrer les commandes slash auprès de Discord avant de les utiliser (guild.commands.set ou enregistrement global via l'API REST). Ajouter une commande /leaderboard avec ORDER BY level DESC, xp DESC LIMIT 10 pour afficher un classement encourage une saine compétition.

Questions fréquentes

Mon bot voit les messages mais n'ajoute pas d'XP, pourquoi ?

Le plus probable : le Message Content Intent est désactivé. Vérifiez que vous avez activé ce privilège dans le Portail développeur et déclaré GatewayIntentBits.MessageContent dans le code. Le bot doit aussi avoir la permission de voir le salon concerné.

Puis-je utiliser MySQL au lieu de better-sqlite3 ?

Oui. SQLite est largement suffisant et sans configuration pour un serveur unique et des communautés de taille moyenne. Si vous exploitez un bot très sollicité sur de nombreux serveurs, MySQL/PostgreSQL ou un cache Redis passe mieux à l'échelle ; la logique des requêtes reste la même.

Comment empêcher l'abus d'XP (spam) ?

Un cooldown par utilisateur est la mesure la plus efficace. Vous pouvez aussi ignorer les messages très courts (par ex. un seul emoji), exclure certains salons (commandes-bot, salon de spam) du gain d'XP et exiger un âge de compte minimum pour les comptes suspects.

Vous voulez un système de niveau et d'XP sur mesure pour votre communauté ? Je peux créer un bot Discord personnalisé avec règles anti-spam, cartes de rang visuelles, récompenses de rôle et classement. Contactez-moi et donnons vie à votre serveur.

Devamı için