Aktif bir topluluk istiyorsanız üyeleri ödüllendirmek işe yarar: bir discord level botu, her mesaj için kullanıcılara XP verir, belirli eşiklerde seviye atlatır ve isterseniz otomatik rol dağıtır. MEE6 ya da Arcane gibi hazır botlar bunu yapar, ama mantığı kendiniz kurduğunuzda sınır yoktur: XP formülünü, anti-spam kurallarını, seviye kartının tasarımını ve ödül rollerini tamamen kontrol edersiniz. Bu yazıda discord.js v14 ile sıfırdan çalışan bir XP sistemi kuracağız.
Nasıl çalışır? XP ve seviye mantığı
Temel fikir basit: bot sunucudaki her mesajı dinler ve mesaj atan üyeye küçük bir miktar XP ekler. XP belli bir eşiğe ulaştığında üye bir sonraki seviyeye geçer. İki şeye dikkat etmek gerekir:
- Anti-spam (cooldown): Üye art arda mesaj yağdırarak XP biriktirmesin diye, kullanıcı başına bir bekleme süresi (örn. 60 saniye) koyarız. Bu süre dolmadan gelen mesajlar XP kazandırmaz.
- Seviye eğrisi: Her seviyenin biraz daha zor olması için XP eşiğini artan bir formülle hesaplarız. Popüler MEE6 formülünde, x seviyesinden x+1'e geçmek için gereken XP:
5 * x² + 50 * x + 100. Yani seviye 0→1 için 100 XP, 1→2 için 155 XP gerekir; rakam her adımda büyür.
Proje kurulumu ve gerekli niyetler (intents)
Önce bir proje klasörü açıp bağımlılıkları kuralım. XP'yi kalıcı saklamak için hafif ve hızlı olan better-sqlite3 kullanacağız:
npm init -y
npm install discord.js better-sqlite3 dotenv
Mesaj içeriğini okuyabilmek için botun Message Content Intent ayrıcalığı şarttır. Bunu hem Discord Developer Portal → Bot → Privileged Gateway Intents altından açın, hem de kodda istediğiniz niyetleri tanımlayın:
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(`Giriş yapıldı: ${c.user.tag}`);
});
client.login(process.env.TOKEN);
Bot tokenını koda gömmeyin; .env dosyasına TOKEN=... şeklinde yazın ve .env'i .gitignore'a ekleyin.
Veritabanı: XP'yi kalıcı saklamak
Bot her yeniden başladığında ilerleme sıfırlanmasın diye XP'yi diske yazmamız gerekir. Sunucu (guild) ve kullanıcı kimliğine göre bir satır tutan basit bir tablo yeterli:
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)
);
`);
// Bir kullanıcının kaydını getir (yoksa oluştur)
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;
}
Sunucu kimliğini de saklamak önemlidir: aynı bot birden çok sunucuda çalışırsa her sunucunun seviyeleri ayrı tutulur.
Mesaj başına XP eklemek
Şimdi işin kalbi: messageCreate olayı. Burada botların ve sistem mesajlarının XP kazanmadığından emin oluyor, cooldown uyguluyor ve seviye atlamasını kontrol ediyoruz:
const cooldowns = new Map(); // userId -> son XP zamanı
const COOLDOWN_MS = 60_000; // 60 saniye
// x seviyesinden x+1'e geçmek için gereken XP
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 arası XP
let { xp, level } = user;
xp += gain;
// Yeterli XP biriktiyse seviye atlat
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);
});
Her mesajda 15–25 arası rastgele XP vermek, MEE6'nın da kullandığı klasik yaklaşımdır; sabit değer yerine küçük bir rastgelelik sistemi tahmin edilemez kılar.
Rol ödülleri ve seviye atlama bildirimi
Seviye atlandığında iki şey yapmak isteriz: kanala kutlama mesajı atmak ve eşiğe denk gelen rolü vermek. Rol eşiklerini basit bir nesnede tutabilirsiniz:
const ROLE_REWARDS = {
5: 'ROL_ID_CIRAK',
10: 'ROL_ID_USTA',
20: 'ROL_ID_EFSANE',
};
async function onLevelUp(message, level) {
await message.channel.send(
`🎉 ${message.author}, tebrikler! Artık **seviye ${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);
}
}
}
Rolün çalışması için botun rolü, vereceği rollerin üstünde olmalı (Sunucu Ayarları → Roller) ve botun Manage Roles yetkisi bulunmalıdır. Aksi halde roles.add izin hatası verir.
Seviye kartı: rank komutu
Kullanıcılar seviyelerini görmek ister. En basit yöntem, bir slash komutuyla metinsel bir kart döndürmektir. Görsel bir kart isterseniz @napi-rs/canvas ile bir PNG çizip AttachmentBuilder ile gönderebilirsiniz; ancak başlangıç için embed yeterli:
// /rank komutu (önceden kaydedilmiş olmalı)
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}** — Seviye ${u.level} | ` +
`${u.xp} / ${need} XP`
);
}
Slash komutlarını kullanmadan önce Discord'a kaydetmeniz gerekir (guild.commands.set veya REST API ile global kayıt). Bir de /leaderboard komutu ekleyip ORDER BY level DESC, xp DESC LIMIT 10 ile sıralama tablosu göstermek topluluğu rekabete teşvik eder.
Sık Sorulan Sorular
Botum mesajları görüyor ama XP eklemiyor, neden?
Büyük ihtimalle Message Content Intent kapalı. Developer Portal'da bu ayrıcalığı açtığınızdan ve kodda GatewayIntentBits.MessageContent tanımladığınızdan emin olun. Ayrıca botun ilgili kanalı görme iznine sahip olması gerekir.
better-sqlite3 yerine MySQL kullanabilir miyim?
Evet. SQLite, tek sunucu ve orta ölçekli topluluklar için fazlasıyla yeterli ve kurulumsuzdur. Çok sayıda sunucuda yüksek trafikli bir bot işletiyorsanız MySQL/PostgreSQL veya Redis önbelleği daha iyi ölçeklenir; sorgu mantığı aynı kalır.
XP'yi kötüye kullanmayı (spam) nasıl engellerim?
Kullanıcı başına cooldown en etkili önlemdir. İsterseniz çok kısa mesajları (örn. tek emoji) yok sayabilir, belirli kanalları (bot komutları, spam kanalı) XP dışı tutabilir ve şüpheli hesaplar için minimum hesap yaşı şartı koyabilirsiniz.
Topluluğunuza özel bir level ve XP sistemi mi istiyorsunuz? Anti-spam kuralları, görsel seviye kartları, rol ödülleri ve sıralama tablosuyla birlikte size özel bir Discord botu kurabilirim. Benimle iletişime geçin ve sunucunuzu canlandıralım.