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

Discord Level Bot: XP-System und Rollenbelohnungen

Wenn du eine aktive Community willst, hilft es, Mitglieder zu belohnen: Ein Discord Level Bot vergibt Nutzern XP für jede Nachricht, lässt sie bei bestimmten Schwellen aufsteigen und verteilt auf Wunsch automatisch Rollen. Fertige Bots wie MEE6 oder Arcane können das, doch wenn du die Logik selbst baust, gibt es keine Grenzen: Du steuerst die XP-Formel, die Anti-Spam-Regeln, das Design der Rang-Karte und die Belohnungsrollen vollständig selbst. In diesem Artikel bauen wir mit discord.js v14 ein funktionierendes XP-System von Grund auf.

Wie es funktioniert: XP- und Level-Logik

Die Grundidee ist einfach: Der Bot lauscht auf jede Nachricht auf dem Server und fügt dem Mitglied, das sie gesendet hat, eine kleine Menge XP hinzu. Erreicht die XP eine Schwelle, steigt das Mitglied ins nächste Level auf. Zwei Dinge verdienen Aufmerksamkeit:

  • Anti-Spam (Cooldown): Damit ein Mitglied nicht durch das Fluten von Nachrichten XP farmen kann, setzen wir eine Wartezeit pro Nutzer (z. B. 60 Sekunden). Nachrichten, die vor Ablauf dieses Fensters gesendet werden, bringen keine XP.
  • Level-Kurve: Damit jedes Level etwas schwerer wird, berechnen wir die XP-Schwelle mit einer steigenden Formel. In der populären MEE6-Formel ist die XP, die nötig ist, um von Level x auf x+1 zu kommen, 5 * x² + 50 * x + 100. Level 0→1 erfordert also 100 XP, 1→2 erfordert 155 XP; die Zahl wächst mit jedem Schritt.

Projekt-Setup und benötigte Intents

Erstelle zuerst einen Projektordner und installiere die Abhängigkeiten. Zum dauerhaften Speichern der XP nutzen wir better-sqlite3, das leicht und schnell ist:

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

Um den Nachrichteninhalt zu lesen, benötigt der Bot das Privileg Message Content Intent. Aktiviere es sowohl im Discord Developer Portal → Bot → Privileged Gateway Intents, als auch durch das Deklarieren der gewünschten Intents im 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(`Angemeldet als ${c.user.tag}`);
});

client.login(process.env.TOKEN);

Schreibe das Bot-Token niemals fest in den Code; lege es in einer .env-Datei als TOKEN=... ab und füge .env zur .gitignore hinzu.

Datenbank: XP dauerhaft speichern

Damit der Fortschritt nicht bei jedem Neustart des Bots zurückgesetzt wird, müssen wir die XP auf die Festplatte schreiben. Eine einfache Tabelle mit einer Zeile pro Server (Guild) und Nutzer reicht aus:

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)
  );
`);

// Datensatz eines Nutzers holen (anlegen, falls nicht vorhanden)
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;
}

Auch das Speichern der Guild-ID ist wichtig: Läuft derselbe Bot auf mehreren Servern, bleiben die Level jedes Servers getrennt.

XP pro Nachricht hinzufügen

Nun das Herzstück: das messageCreate-Event. Hier stellen wir sicher, dass Bots und Systemnachrichten keine XP erhalten, wenden den Cooldown an und prüfen auf Level-Ups:

const cooldowns = new Map(); // userId -> letzter XP-Zeitpunkt
const COOLDOWN_MS = 60_000;  // 60 Sekunden

// XP, die nötig ist, um von Level x auf x+1 zu kommen
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;

  // Aufsteigen, solange genug XP angesammelt wurde
  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);
});

Pro Nachricht 15 bis 25 zufällige XP zu vergeben ist der klassische Ansatz, den auch MEE6 nutzt; ein wenig Zufall statt eines festen Werts macht das System schwerer ausnutzbar.

Rollenbelohnungen und die Level-Up-Ankündigung

Bei einem Level-Up wollen wir zwei Dinge: eine Glückwunschnachricht in den Kanal posten und die zu dieser Schwelle gehörende Rolle vergeben. Du kannst die Rollenschwellen in einem einfachen Objekt halten:

const ROLE_REWARDS = {
  5:  'ROLLEN_ID_LEHRLING',
  10: 'ROLLEN_ID_MEISTER',
  20: 'ROLLEN_ID_LEGENDE',
};

async function onLevelUp(message, level) {
  await message.channel.send(
    `🎉 ${message.author}, Glückwunsch! Du bist jetzt **Level ${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);
    }
  }
}

Damit Rollen funktionieren, muss die Rolle des Bots über den Rollen stehen, die er vergibt (Servereinstellungen → Rollen), und der Bot benötigt die Berechtigung Manage Roles (Rollen verwalten). Andernfalls wirft roles.add einen Berechtigungsfehler.

Die Rang-Karte: ein rank-Befehl

Nutzer möchten ihr Level sehen. Der einfachste Ansatz ist, eine Textkarte über einen Slash-Befehl zurückzugeben. Für eine grafische Karte kannst du mit @napi-rs/canvas ein PNG zeichnen und es mit AttachmentBuilder senden; für den Anfang reicht jedoch ein Embed:

// /rank-Befehl (muss vorher registriert sein)
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}** — Level ${u.level} | ` +
    `${u.xp} / ${need} XP`
  );
}

Du musst Slash-Befehle bei Discord registrieren, bevor du sie verwendest (guild.commands.set oder globale Registrierung über die REST-API). Ein /leaderboard-Befehl mit ORDER BY level DESC, xp DESC LIMIT 10, der eine Rangliste anzeigt, fördert einen gesunden Wettbewerb.

Häufige Fragen

Mein Bot sieht Nachrichten, fügt aber keine XP hinzu — warum?

Höchstwahrscheinlich ist das Message Content Intent deaktiviert. Stelle sicher, dass du dieses Privileg im Developer Portal aktiviert und GatewayIntentBits.MessageContent im Code deklariert hast. Der Bot braucht außerdem die Berechtigung, den betreffenden Kanal zu sehen.

Kann ich MySQL statt better-sqlite3 verwenden?

Ja. SQLite ist für einen einzelnen Server und mittelgroße Communitys mehr als ausreichend und benötigt keine Einrichtung. Betreibst du einen stark frequentierten Bot auf vielen Servern, skaliert MySQL/PostgreSQL oder ein Redis-Cache besser; die Abfragelogik bleibt gleich.

Wie verhindere ich XP-Missbrauch (Spam)?

Ein Cooldown pro Nutzer ist die wirksamste Maßnahme. Du kannst zudem sehr kurze Nachrichten ignorieren (z. B. ein einzelnes Emoji), bestimmte Kanäle (Bot-Befehle, Spam-Kanal) von der XP-Vergabe ausschließen und für verdächtige Konten ein Mindestalter verlangen.

Möchtest du ein maßgeschneidertes Level- und XP-System für deine Community? Ich baue dir einen individuellen Discord-Bot mit Anti-Spam-Regeln, grafischen Rang-Karten, Rollenbelohnungen und einer Rangliste. Kontaktiere mich und lass uns deinen Server zum Leben erwecken.

Devamı için