Ein Discord Economy-Bot fügt deinem Server eine Gamification-Ebene hinzu, die Mitglieder aktiv hält: Sie verdienen eine virtuelle Währung, holen tägliche Belohnungen ab und geben ihr Erspartes in einem Shop aus, um Rollen oder Items zu kaufen. In dieser Anleitung bauen wir mit discord.js von Grund auf ein funktionierendes Wirtschaftssystem mit Befehlen für Kontostand, Tagesbonus und Shop. Wir verwenden moderne Slash Commands und speichern die Daten in einer persistenten Datenbank.
Voraussetzungen und Projekteinrichtung
Zum Start benötigst du Node.js 18 oder neuer, einen Code-Editor und eine im Discord Developer Portal erstellte Bot-Anwendung. Der Message Content-Intent ist hier nicht erforderlich, da wir Slash Commands verwenden, aber beim Einladen des Bots auf deinen Server musst du die Scopes applications.commands und bot auswählen.
Erstelle einen neuen Ordner und installiere die Abhängigkeiten:
npm init -y
npm install discord.js better-sqlite3 dotenv
Statt Token und Anwendungs-ID fest in den Code zu schreiben, bewahren wir sie in einer .env-Datei auf:
DISCORD_TOKEN=dein_bot_token
CLIENT_ID=anwendungs_id
GUILD_ID=test_server_id
Datenbankschema
Die Wirtschaftsdaten müssen persistent sein; Kontostände dürfen nicht bei jedem Neustart des Bots zurückgesetzt werden. Wir verwenden better-sqlite3, weil die Einrichtung trivial ist. Eine einzige Tabelle reicht aus:
const Database = require('better-sqlite3');
const db = new Database('economy.db');
db.exec(`
CREATE TABLE IF NOT EXISTS users (
user_id TEXT PRIMARY KEY,
balance INTEGER NOT NULL DEFAULT 0,
last_daily INTEGER
);
`);
function getUser(id) {
let row = db.prepare('SELECT * FROM users WHERE user_id = ?').get(id);
if (!row) {
db.prepare('INSERT INTO users (user_id) VALUES (?)').run(id);
row = { user_id: id, balance: 0, last_daily: null };
}
return row;
}
function addBalance(id, amount) {
getUser(id);
db.prepare('UPDATE users SET balance = balance + ? WHERE user_id = ?').run(amount, id);
}
Das Feld last_daily speichert als Unix-Zeitstempel, wann die Tagesbelohnung zuletzt abgeholt wurde. Jede Währungsbewegung über eine einzige addBalance-Funktion laufen zu lassen, erleichtert es später erheblich, Logging oder Transaktionssicherheit hinzuzufügen.
Befehle definieren und registrieren
In aktuellen Versionen von discord.js müssen Slash Commands separat bei der Discord-API registriert werden. Während der Entwicklung wird die Registrierung bei einem einzelnen Server sofort aktualisiert; die globale Registrierung kann bis zu einer Stunde zur Verbreitung benötigen. Wir schreiben ein separates deploy-commands.js-Skript:
const { REST, Routes, SlashCommandBuilder } = require('discord.js');
require('dotenv').config();
const commands = [
new SlashCommandBuilder().setName('balance').setDescription('Zeigt deinen Kontostand'),
new SlashCommandBuilder().setName('daily').setDescription('Hole deinen Tagesbonus'),
new SlashCommandBuilder().setName('shop').setDescription('Listet den Shop auf'),
new SlashCommandBuilder()
.setName('buy')
.setDescription('Kaufe ein Item aus dem Shop')
.addStringOption(o =>
o.setName('item').setDescription('Item-Schlüssel').setRequired(true)),
].map(c => c.toJSON());
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
rest.put(
Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
{ body: commands }
).then(() => console.log('Befehle registriert.'));
Logik für Kontostand und Tagesbonus
Jetzt verarbeiten wir die Interaktionen in der Hauptdatei des Bots. Der entscheidende Teil des Tagesbonus ist die Cooldown-Prüfung, die einen Nutzer auf eine Abholung pro 24 Stunden beschränkt. Das machen wir durch einen Vergleich mit last_daily:
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const DAILY_AMOUNT = 250;
const DAILY_COOLDOWN = 24 * 60 * 60 * 1000; // 24 Stunden (ms)
client.on('interactionCreate', async (i) => {
if (!i.isChatInputCommand()) return;
const user = getUser(i.user.id);
if (i.commandName === 'balance') {
await i.reply(`💰 Dein Kontostand: **${user.balance}** Münzen.`);
}
if (i.commandName === 'daily') {
const now = Date.now();
if (user.last_daily && now - user.last_daily < DAILY_COOLDOWN) {
const rest = user.last_daily + DAILY_COOLDOWN - now;
const stunden = Math.ceil(rest / (60 * 60 * 1000));
return i.reply(`⏳ Warte ~${stunden}h auf deine nächste Belohnung.`);
}
addBalance(i.user.id, DAILY_AMOUNT);
db.prepare('UPDATE users SET last_daily = ? WHERE user_id = ?').run(now, i.user.id);
await i.reply(`🎁 Du hast ${DAILY_AMOUNT} Münzen verdient!`);
}
});
client.login(process.env.DISCORD_TOKEN);
Zeitstempel in Millisekunden zu speichern und zu vergleichen, beseitigt Zeitzonenprobleme vollständig. Eine Regel „24 Stunden seit der letzten Abholung" ist sowohl fairer als auch robuster umzusetzen als ein System, das „um Mitternacht zurücksetzt".
Shop und Kaufablauf
Für den Shop reicht es, die Items als Objekt im Code zu definieren; eine separate Tabelle ist auf kleinen Servern nicht nötig. Jedes Item hat einen Schlüssel, einen Namen, einen Preis und optional die ID einer Discord-Rolle, die es gewährt:
const SHOP = {
vip: { name: 'VIP-Rolle', price: 1000, roleId: 'ROLLEN_ID' },
farbe: { name: 'Farbiger Name', price: 500, roleId: 'ROLLEN_ID_2' },
};
Im Kaufbefehl prüfen wir zuerst den Kontostand, ziehen die Kosten ab, wenn er ausreicht, und gewähren die Rolle. Die Kontostandsprüfung und den Abzug in einem Schritt auszuführen, verhindert einen negativen Kontostand bei Race Conditions wie Doppelklicks:
if (i.commandName === 'shop') {
const liste = Object.entries(SHOP)
.map(([k, v]) => `• \`${k}\` — ${v.name}: ${v.price} Münzen`)
.join('\n');
return i.reply(`🛒 **Shop**\n${liste}`);
}
if (i.commandName === 'buy') {
const key = i.options.getString('item');
const item = SHOP[key];
if (!item) return i.reply('❌ Dieses Item existiert nicht.');
if (user.balance < item.price) return i.reply('❌ Nicht genug Münzen.');
addBalance(i.user.id, -item.price);
if (item.roleId) {
const member = await i.guild.members.fetch(i.user.id);
await member.roles.add(item.roleId).catch(() => null);
}
await i.reply(`✅ **${item.name}** gekauft!`);
}
Damit die Rollenvergabe funktioniert, muss die eigene Rolle des Bots in der Liste höher stehen als die Rolle, die er vergibt, und der Bot benötigt die Berechtigung „Rollen verwalten". Andernfalls gibt Discord einen Berechtigungsfehler zurück; diesen mit .catch() sauber abzufangen, damit der Nutzer informiert wird, ohne dass der Bot abstürzt, ist eine gute Angewohnheit.
Sicherheit und Verbesserungstipps
Das Kernsystem ist fertig, doch ein paar Punkte verdienen vor dem Produktiveinsatz Aufmerksamkeit. Wenn du ein System hinzufügst, das pro Nachricht Währung belohnt, setze einen kurzen Cooldown pro Nutzer, um Spam zu verhindern. Validiere Kontostände immer serverseitig und vertraue niemals einem Wert, der vom Client kommt. Um den Bot rund um die Uhr online zu halten, ist es am zuverlässigsten, ihn auf einem kleinen VPS mit einem Prozessmanager wie pm2 laufen zu lassen.
Häufige Fragen
Kann ich MySQL als Datenbank verwenden?
Ja. SQLite ist wegen der einfachen Einrichtung ideal, aber für einen großen Bot, der über mehrere Server läuft, passt MySQL oder PostgreSQL besser. Die Abfragelogik bleibt gleich; du tauschst nur den Datenbanktreiber und die Verbindung aus.
Warum wird der Tagesbonus mit einem Zeitstempel geprüft?
Weil „setzt jede Mitternacht zurück"-Ansätze von Zeitzonenunterschieden betroffen und anfällig für Missbrauch sind. Den Zeitpunkt der letzten Abholung zu speichern und zu prüfen, ob 24 Stunden vergangen sind, ist fair und zeitzonenunabhängig.
Verschwindet eine gekaufte Rolle, wenn der Bot offline geht?
Nein. Die Rolle wird dem Mitglied auf Discord-Seite zugewiesen und ist dauerhaft; sie bleibt, selbst wenn der Bot ausfällt. Nur der Münzkontostand liegt in der Datenbank, achte also darauf, sie nicht zu verlieren.
Möchtest du einen professionellen, skalierbaren Economy-Bot für deinen Server? Ich kann einen maßgeschneiderten Bot nach deinen Bedürfnissen entwickeln – mit eigenen Befehlen, Saisons und Panel-Integration. Um dein Projekt zu besprechen, nimm Kontakt mit mir auf.