Rehber - Moderasyon Botu Yapımı: Ban, Kick, Timeout, Uyarı (Warn) Sistemi | SroCave - SRO Destek Forumu

Rehber Moderasyon Botu Yapımı: Ban, Kick, Timeout, Uyarı (Warn) Sistemi

  • Konbuyu başlatan Konbuyu başlatan Jhaeger
  • Başlangıç tarihi Başlangıç tarihi
  • Cevaplar Cevaplar 0
  • Görüntüleme Görüntüleme 5

Jhaeger

King of the Underworld
Ayın En Aktif Üyesi
Katılım
9 Şub 2024
Mesajlar
946
Tepkime puanı
1,780
Puanları
93
jhaeger
Moderasyon Botu Yapımı: Ban, Kick, Timeout, Uyarı (Warn) Sistemi
discord.js v14 ile slash komutlu moderasyon botu​



Selamlar SroCave Ailesi!

Bu rehberde sunucunuzu yönetmeyi kolaylaştıracak bir moderasyon botu yapacağız. Botumuzda şu komutlar olacak:
  • /ban : Kullanıcıyı sunucudan yasaklar
  • /kick : Kullanıcıyı sunucudan atar
  • /timeout ve /timeout-kaldir : Kullanıcıyı susturur / susturmayı kaldırır
  • /warn : Kullanıcıya uyarı verir (3 uyarıda otomatik timeout)
  • /uyarilar : Kullanıcının uyarılarını listeler
  • /uyarilari-sil : Kullanıcının uyarılarını temizler

📝 Not: Slash komutlarının temelini bilmiyorsanız önce şu konuya göz atın: Discord Botlarında Slash Commands (/) Sistemi Nasıl Kurulur?



⚙️ Gerekli Kurulum​

Node.js 18 veya üzeri gereklidir. Proje klasörünüzde:

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

📄 .env Dosyası
Proje klasörüne .env dosyası oluşturup şunları yazın:
Kod:
TOKEN=BOT_TOKENINIZ
CLIENT_ID=BOT_UYGULAMA_IDSI
GUILD_ID=SUNUCU_IDSI

⚠️ Uyarı: .env dosyanızı asla başkalarıyla paylaşmayın ve .gitignore içine ekleyin. Token güvenliği için: Discord Bot Token'ı Nedir? Güvenli Nasıl Saklanır?



🔑 Botun İhtiyacı Olan Yetkiler​

Botu sunucuya davet ederken (OAuth2 > URL Generator) şu yetkileri verin:
  • Ban Members : /ban için
  • Kick Members : /kick için
  • Moderate Members : /timeout ve /warn için
  • Scope olarak bot ve applications.commands

⚠️ Çok Önemli: Botun rolü, işlem yapacağı üyelerin rollerinden daha yukarıda olmalıdır. Sunucu Ayarları > Roller bölümünden bot rolünü yukarı taşıyın. Aksi halde Missing Permissions hatası alırsınız.



🤖 Tam Bot Kodu (index.js)​


JavaScript:
require('dotenv').config();
const {
    Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder,
    PermissionFlagsBits, EmbedBuilder, MessageFlags
} = require('discord.js');
const Database = require('better-sqlite3');

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

// ---------- Veritabanı ----------
const db = new Database('moderasyon.db');
db.exec(`
    CREATE TABLE IF NOT EXISTS warns (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        guild_id TEXT,
        user_id TEXT,
        mod_id TEXT,
        reason TEXT,
        date TEXT
    )
`);

// ---------- Yardımcı Fonksiyonlar ----------
const kullaniciSecenegi = o => o.setName('kullanici').setDescription('Hedef kullanıcı').setRequired(true);
const sebepSecenegi = o => o.setName('sebep').setDescription('İşlem sebebi');

const bilgi = (baslik, renk, aciklama) =>
    new EmbedBuilder().setTitle(baslik).setColor(renk).setDescription(aciklama).setTimestamp();

const hataGonder = (interaction, mesaj) =>
    interaction.reply({ content: `❌ ${mesaj}`, flags: MessageFlags.Ephemeral });

// Hedef üyeye işlem yapılabilir mi? (hiyerarşi kontrolü)
function kontrol(interaction, uye) {
    if (uye.id === interaction.user.id) return 'Kendinize işlem uygulayamazsınız.';
    if (uye.id === interaction.guild.ownerId) return 'Sunucu sahibine işlem uygulanamaz.';
    if (uye.id === interaction.client.user.id) return 'Bota işlem uygulayamazsınız.';
    const sahipMi = interaction.user.id === interaction.guild.ownerId;
    if (!sahipMi && uye.roles.highest.position >= interaction.member.roles.highest.position) {
        return 'Sizden üst veya eşit roldeki birine işlem uygulayamazsınız.';
    }
    return null;
}

// ---------- Komut Tanımları ----------
const commands = [
    new SlashCommandBuilder().setName('ban').setDescription('Kullanıcıyı sunucudan yasaklar')
        .addUserOption(kullaniciSecenegi).addStringOption(sebepSecenegi)
        .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),

    new SlashCommandBuilder().setName('kick').setDescription('Kullanıcıyı sunucudan atar')
        .addUserOption(kullaniciSecenegi).addStringOption(sebepSecenegi)
        .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),

    new SlashCommandBuilder().setName('timeout').setDescription('Kullanıcıyı belirli süre susturur')
        .addUserOption(kullaniciSecenegi)
        .addIntegerOption(o => o.setName('dakika').setDescription('Süre (dakika, en fazla 40320)')
            .setRequired(true).setMinValue(1).setMaxValue(40320))
        .addStringOption(sebepSecenegi)
        .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),

    new SlashCommandBuilder().setName('timeout-kaldir').setDescription('Kullanıcının susturmasını kaldırır')
        .addUserOption(kullaniciSecenegi)
        .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),

    new SlashCommandBuilder().setName('warn').setDescription('Kullanıcıya uyarı verir')
        .addUserOption(kullaniciSecenegi).addStringOption(sebepSecenegi)
        .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),

    new SlashCommandBuilder().setName('uyarilar').setDescription('Kullanıcının uyarılarını listeler')
        .addUserOption(kullaniciSecenegi)
        .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),

    new SlashCommandBuilder().setName('uyarilari-sil').setDescription('Kullanıcının tüm uyarılarını siler')
        .addUserOption(kullaniciSecenegi)
        .setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
].map(c => c.toJSON());

// ---------- Komutları Discord'a Kaydet ----------
const rest = new REST({ version: '10' }).setToken(process.env.TOKEN);

client.once('ready', async () => {
    console.log(`Bot giriş yaptı: ${client.user.tag}`);
    try {
        await rest.put(
            Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
            { body: commands }
        );
        console.log('Slash komutlar yüklendi!');
    } catch (error) {
        console.error(error);
    }
});

// ---------- Komutları Çalıştır ----------
client.on('interactionCreate', async interaction => {
    if (!interaction.isChatInputCommand()) return;

    const { commandName, options, guild } = interaction;
    const hedef = options.getUser('kullanici');
    const sebep = options.getString('sebep') || 'Sebep belirtilmedi';
    const uye = hedef ? await guild.members.fetch(hedef.id).catch(() => null) : null;

    try {
        // ----- BAN -----
        if (commandName === 'ban') {
            if (uye) {
                const hata = kontrol(interaction, uye);
                if (hata) return hataGonder(interaction, hata);
                if (!uye.bannable) return hataGonder(interaction, 'Bu kullanıcıyı banlayamam. Bot rolümü yukarı taşıyın.');
            }
            await hedef.send(`🔨 **${guild.name}** sunucusundan yasaklandınız.\nSebep: ${sebep}`).catch(() => {});
            await guild.members.ban(hedef, { reason: `${interaction.user.tag}: ${sebep}` });
            return interaction.reply({
                embeds: [bilgi('🔨 Kullanıcı Yasaklandı', 0xED4245,
                    `**Kullanıcı:** ${hedef.tag}\n**Yetkili:** ${interaction.user}\n**Sebep:** ${sebep}`)]
            });
        }

        // Bundan sonraki komutlar için kullanıcı sunucuda olmalı
        if (['kick', 'timeout', 'timeout-kaldir', 'warn'].includes(commandName)) {
            if (!uye) return hataGonder(interaction, 'Bu kullanıcı sunucuda bulunamadı.');
            const hata = kontrol(interaction, uye);
            if (hata) return hataGonder(interaction, hata);
        }

        // ----- KICK -----
        if (commandName === 'kick') {
            if (!uye.kickable) return hataGonder(interaction, 'Bu kullanıcıyı atamam. Bot rolümü yukarı taşıyın.');
            await hedef.send(`👢 **${guild.name}** sunucusundan atıldınız.\nSebep: ${sebep}`).catch(() => {});
            await uye.kick(`${interaction.user.tag}: ${sebep}`);
            return interaction.reply({
                embeds: [bilgi('👢 Kullanıcı Atıldı', 0xFEE75C,
                    `**Kullanıcı:** ${hedef.tag}\n**Yetkili:** ${interaction.user}\n**Sebep:** ${sebep}`)]
            });
        }

        // ----- TIMEOUT -----
        if (commandName === 'timeout') {
            const dakika = options.getInteger('dakika');
            if (!uye.moderatable) return hataGonder(interaction, 'Bu kullanıcıyı susturamam. Bot rolümü yukarı taşıyın.');
            await uye.timeout(dakika * 60 * 1000, `${interaction.user.tag}: ${sebep}`);
            return interaction.reply({
                embeds: [bilgi('🔇 Kullanıcı Susturuldu', 0xFEE75C,
                    `**Kullanıcı:** ${hedef}\n**Süre:** ${dakika} dakika\n**Yetkili:** ${interaction.user}\n**Sebep:** ${sebep}`)]
            });
        }

        // ----- TIMEOUT KALDIR -----
        if (commandName === 'timeout-kaldir') {
            if (!uye.moderatable) return hataGonder(interaction, 'Bu kullanıcıya işlem yapamam.');
            await uye.timeout(null);
            return interaction.reply({
                embeds: [bilgi('🔊 Susturma Kaldırıldı', 0x57F287, `**Kullanıcı:** ${hedef}\n**Yetkili:** ${interaction.user}`)]
            });
        }

        // ----- WARN -----
        if (commandName === 'warn') {
            db.prepare('INSERT INTO warns (guild_id, user_id, mod_id, reason, date) VALUES (?, ?, ?, ?, ?)')
                .run(guild.id, hedef.id, interaction.user.id, sebep, new Date().toLocaleString('tr-TR'));

            const sayi = db.prepare('SELECT COUNT(*) AS c FROM warns WHERE guild_id = ? AND user_id = ?')
                .get(guild.id, hedef.id).c;

            await hedef.send(`⚠️ **${guild.name}** sunucusunda uyarı aldınız.\nSebep: ${sebep}\nToplam uyarı: ${sayi}`).catch(() => {});

            let ek = '';
            if (sayi >= 3 && uye.moderatable) {
                await uye.timeout(60 * 60 * 1000, '3 uyarıya ulaştı (otomatik)');
                ek = '\n\n🔇 **3 uyarıya ulaştığı için 1 saat susturuldu.**';
            }

            return interaction.reply({
                embeds: [bilgi('⚠️ Uyarı Verildi', 0xFEE75C,
                    `**Kullanıcı:** ${hedef}\n**Yetkili:** ${interaction.user}\n**Sebep:** ${sebep}\n**Toplam Uyarı:** ${sayi}${ek}`)]
            });
        }

        // ----- UYARILAR -----
        if (commandName === 'uyarilar') {
            const liste = db.prepare('SELECT * FROM warns WHERE guild_id = ? AND user_id = ? ORDER BY id DESC LIMIT 10')
                .all(guild.id, hedef.id);

            if (liste.length === 0) {
                return interaction.reply({ embeds: [bilgi('📋 Uyarılar', 0x57F287, `${hedef} kullanıcısının hiç uyarısı yok.`)] });
            }

            const metin = liste.map(w => `**#${w.id}** • ${w.reason}\n└ <@${w.mod_id}> • ${w.date}`).join('\n\n');
            return interaction.reply({
                embeds: [bilgi(`📋 ${hedef.username} - Son ${liste.length} Uyarı`, 0x5865F2, metin)]
            });
        }

        // ----- UYARILARI SİL -----
        if (commandName === 'uyarilari-sil') {
            const sonuc = db.prepare('DELETE FROM warns WHERE guild_id = ? AND user_id = ?').run(guild.id, hedef.id);
            return interaction.reply({
                embeds: [bilgi('🧹 Uyarılar Temizlendi', 0x57F287, `${hedef} kullanıcısının **${sonuc.changes}** uyarısı silindi.`)]
            });
        }
    } catch (error) {
        console.error(error);
        const mesaj = { content: '❌ Bir hata oluştu. Bot yetkilerini ve rol sırasını kontrol edin.', flags: MessageFlags.Ephemeral };
        if (interaction.replied || interaction.deferred) interaction.followUp(mesaj);
        else interaction.reply(mesaj);
    }
});

client.login(process.env.TOKEN);

Botu başlatmak için:
Bash:
node index.js



📖 Kod Açıklaması​

  • setDefaultMemberPermissions: Komutu sadece ilgili yetkiye sahip kişilerin görmesini ve kullanmasını sağlar.
  • kontrol(): Kendinize, sunucu sahibine veya üst rolündeki birine işlem yapılmasını engeller.
  • bannable / kickable / moderatable: Botun o kişiye işlem yapıp yapamayacağını söyler (rol sırası kontrolü).
  • hedef.send(): Kullanıcıya özel mesaj atar. DM'leri kapalıysa hata vermemesi için .catch(() => {}) kullanılır.
  • better-sqlite3: Uyarıları moderasyon.db dosyasında kalıcı olarak saklar.
  • flags: MessageFlags.Ephemeral: Hata mesajını sadece komutu kullanan kişiye gösterir.



🧪 Örnek Kullanım​

Kod:
/ban kullanici:@Ahmet sebep:Reklam yapmak
/kick kullanici:@Ahmet sebep:Kurallara uymamak
/timeout kullanici:@Ahmet dakika:30 sebep:Spam
/timeout-kaldir kullanici:@Ahmet
/warn kullanici:@Ahmet sebep:Küfür
/uyarilar kullanici:@Ahmet
/uyarilari-sil kullanici:@Ahmet



🧯 Sık Karşılaşılan Hatalar​

  • Missing Permissions: Bot rolü hedefin rolünden aşağıda veya botta gerekli yetki yok.
  • Komutlar görünmüyor: Botu applications.commands scope'u ile davet etmemiş olabilirsiniz veya GUILD_ID yanlıştır.
  • Cannot find module 'better-sqlite3': npm install better-sqlite3 komutunu çalıştırın.
  • Unknown Interaction: Bot 3 saniye içinde cevap veremedi, işlem uzunsa interaction.deferReply() kullanın.

Hata çözümleri için ayrıntılı konumuz: Discord Bot Hataları ve Çözümleri



💡 Geliştirme Fikirleri​

  • Moderasyon işlemlerini bir log kanalına göndermek
  • Uyarı eşiğini (3) ve otomatik ceza süresini ayarlanabilir yapmak
  • /temizle komutu ile toplu mesaj silmek
  • /unban komutu eklemek
  • Uyarılara tarih bazlı otomatik sona erme eklemek



🔗 Faydalı Kaynaklar​


🏁 Sonuç​

Artık ban, kick, timeout ve kalıcı uyarı sistemiyle çalışan, rol hiyerarşisini de kontrol eden bir moderasyon botunuz var. Yetkileri doğru verdiğiniz sürece sunucunuzu güvenle yönetebilirsiniz.

⚠️ Not: Botunuzu geliştirirken kodlarınızı yedeklemeyi ve veritabanı dosyasını güvenli tutmayı unutmayın!

Sorularınızı aşağıya yazabilirsiniz, herkese kolay gelsin! 💜​
 
Etiketler Etiketler
#bot #botmaker #communitymanager #communityowner #discord #discordmanager #gifty #guardcommunity #jhaeger #mmoelite

Geri
Üst Alt