Rehber - Buton, Select Menu ve Modal Kullanımı (discord.js v14 / discord.py) | SroCave - SRO Destek Forumu

Rehber Buton, Select Menu ve Modal Kullanımı (discord.js v14 / discord.py)

  • 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
Buton, Select Menu ve Modal Kullanımı
discord.js v14 ve discord.py ile etkileşimli bot paneli​



Selamlar SroCave Ailesi!

Butonlu Giveaway konusunda butonların gücünü görmüştük. Bu rehberde işi bir adım ileri taşıyıp Discord'un üç temel etkileşim bileşenini birlikte öğreneceğiz:
  • Buton (Button): Tek tıkla işlem yaptıran düğmeler
  • Select Menu: Açılır listeden seçim yaptıran menüler
  • Modal: Kullanıcıdan yazılı bilgi alan açılır formlar

📝 Not: Butonların temel mantığını görmek için Discord Botlarında Butonlu Giveaway (Çekiliş) Sistemi Yapımı konumuza, slash komutların temeli için Slash Commands (/) Sistemi Nasıl Kurulur? konumuza göz atabilirsiniz.



🧠 Mantık: Bileşenler Nasıl Çalışır?​

Üç bileşenin de çalışma düzeni aynıdır:
  1. Bot mesajına bileşeni ekler ve ona benzersiz bir custom ID verir.
  2. Kullanıcı bileşene tıkladığında Discord bota bir interaction (etkileşim) gönderir.
  3. Bot custom ID'ye bakarak hangi bileşene tıklandığını anlar ve cevap verir.

⏱️ Önemli: Her etkileşime 3 saniye içinde cevap vermelisiniz, aksi halde kullanıcı Bu etkileşim başarısız oldu hatasını görür. İşlem uzunsa önce deferReply() kullanın.



🟨 Bölüm 1: discord.js v14​


1️⃣ Kurulum
Bash:
npm install discord.js dotenv

2️⃣ .env Dosyası
Kod:
TOKEN=BOT_TOKENINIZ
CLIENT_ID=BOT_UYGULAMA_IDSI
GUILD_ID=SUNUCU_IDSI

3️⃣ Tam Bot Kodu (index.js)
Bu bot /panel komutuyla 3 buton + 1 link butonu + 1 select menü içeren bir mesaj gönderir. Form Aç butonu modal açar.

JavaScript:
require('dotenv').config();
const {
    Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder, MessageFlags,
    ActionRowBuilder, ButtonBuilder, ButtonStyle,
    StringSelectMenuBuilder, StringSelectMenuOptionBuilder,
    ModalBuilder, TextInputBuilder, TextInputStyle
} = require('discord.js');

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

// ---------- Bileşen Oluşturucular ----------
const butonSatiri = (sayi) => new ActionRowBuilder().addComponents(
    new ButtonBuilder().setCustomId('selam').setLabel('Selam Ver').setEmoji('👋').setStyle(ButtonStyle.Success),
    new ButtonBuilder().setCustomId('sayac').setLabel(`Tıklama: ${sayi}`).setStyle(ButtonStyle.Primary),
    new ButtonBuilder().setCustomId('form_ac').setLabel('Form Aç').setEmoji('📝').setStyle(ButtonStyle.Secondary),
    new ButtonBuilder().setLabel('Dokümantasyon').setStyle(ButtonStyle.Link).setURL('https://discordjs.guide/')
);

const menuSatiri = () => new ActionRowBuilder().addComponents(
    new StringSelectMenuBuilder()
        .setCustomId('renk_menu')
        .setPlaceholder('Bir renk seçin...')
        .addOptions(
            new StringSelectMenuOptionBuilder().setLabel('Kırmızı').setValue('kirmizi').setDescription('Ateş gibi').setEmoji('🔴'),
            new StringSelectMenuOptionBuilder().setLabel('Mavi').setValue('mavi').setDescription('Deniz gibi').setEmoji('🔵'),
            new StringSelectMenuOptionBuilder().setLabel('Yeşil').setValue('yesil').setDescription('Orman gibi').setEmoji('🟢')
        )
);

const renkler = { kirmizi: 'Kırmızı 🔴', mavi: 'Mavi 🔵', yesil: 'Yeşil 🟢' };

// ---------- Komut Kaydı ----------
const commands = [
    new SlashCommandBuilder()
        .setName('panel')
        .setDescription('Buton, menü ve form içeren panel gönderir')
        .toJSON()
];

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

// ---------- Etkileşimler ----------
client.on('interactionCreate', async interaction => {
    try {
        // ----- /panel komutu -----
        if (interaction.isChatInputCommand() && interaction.commandName === 'panel') {
            return interaction.reply({
                content: '🎛️ **SroCave Kontrol Paneli**\nAşağıdaki bileşenleri deneyin!',
                components: [menuSatiri(), butonSatiri(0)]
            });
        }

        // ----- Butonlar -----
        if (interaction.isButton()) {
            if (interaction.customId === 'selam') {
                return interaction.reply({
                    content: `Merhaba ${interaction.user}! 👋`,
                    flags: MessageFlags.Ephemeral
                });
            }

            if (interaction.customId === 'sayac') {
                // Butonun üzerindeki sayıyı oku, 1 artır, mesajı güncelle
                const sayi = parseInt(interaction.component.label.replace(/\D/g, ''), 10) + 1;
                return interaction.update({ components: [menuSatiri(), butonSatiri(sayi)] });
            }

            if (interaction.customId === 'form_ac') {
                const modal = new ModalBuilder()
                    .setCustomId('geri_bildirim')
                    .setTitle('Geri Bildirim Formu');

                const konu = new TextInputBuilder()
                    .setCustomId('konu')
                    .setLabel('Konu')
                    .setStyle(TextInputStyle.Short)
                    .setPlaceholder('Kısaca konuyu yazın')
                    .setMaxLength(50)
                    .setRequired(true);

                const mesaj = new TextInputBuilder()
                    .setCustomId('mesaj')
                    .setLabel('Mesajınız')
                    .setStyle(TextInputStyle.Paragraph)
                    .setPlaceholder('Düşüncelerinizi buraya yazın...')
                    .setMaxLength(500)
                    .setRequired(true);

                modal.addComponents(
                    new ActionRowBuilder().addComponents(konu),
                    new ActionRowBuilder().addComponents(mesaj)
                );

                return interaction.showModal(modal);
            }
        }

        // ----- Select Menu -----
        if (interaction.isStringSelectMenu() && interaction.customId === 'renk_menu') {
            const secilen = interaction.values[0];
            return interaction.reply({
                content: `Seçtiğiniz renk: **${renkler[secilen]}**`,
                flags: MessageFlags.Ephemeral
            });
        }

        // ----- Modal Gönderimi -----
        if (interaction.isModalSubmit() && interaction.customId === 'geri_bildirim') {
            const konu = interaction.fields.getTextInputValue('konu');
            const mesaj = interaction.fields.getTextInputValue('mesaj');

            return interaction.reply({
                content: `✅ Teşekkürler ${interaction.user}!\n**Konu:** ${konu}\n**Mesaj:** ${mesaj}`,
                allowedMentions: { parse: [] }, // Kullanıcı yazdığı metinle etiket atamasın
                flags: MessageFlags.Ephemeral
            });
        }
    } catch (error) {
        console.error(error);
        const mesaj = { content: '❌ Bir hata oluştu.', flags: MessageFlags.Ephemeral };
        if (interaction.replied || interaction.deferred) interaction.followUp(mesaj).catch(() => {});
        else interaction.reply(mesaj).catch(() => {});
    }
});

client.login(process.env.TOKEN);

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

📖 Kod Açıklaması
  • ActionRowBuilder: Bileşenleri satırlar halinde tutar. Butonlar ve menüler her zaman bir satırın içine konur.
  • ButtonStyle: Buton rengini belirler: Primary (mavi), Secondary (gri), Success (yeşil), Danger (kırmızı), Link (bağlantı).
  • interaction.update(): Yeni mesaj atmadan, tıklanan mesajı günceller (sayaç örneği).
  • interaction.values: Select menüde seçilen değerlerin listesidir.
  • showModal(): Formu açar. Modal, etkileşime verilen ilk cevap olmalıdır.
  • fields.getTextInputValue(): Modaldaki alanın değerini custom ID ile okur.
  • MessageFlags.Ephemeral: Cevabı sadece etkileşimi başlatan kişiye gösterir.



🟦 Bölüm 2: discord.py​


1️⃣ Kurulum
Bash:
pip install -U discord.py python-dotenv

2️⃣ .env Dosyası
Kod:
TOKEN=BOT_TOKENINIZ

3️⃣ Tam Bot Kodu (bot.py)

Python:
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()

intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)

RENKLER = {"kirmizi": "Kırmızı 🔴", "mavi": "Mavi 🔵", "yesil": "Yeşil 🟢"}


# ---------- Modal (Form) ----------
class GeriBildirimModal(discord.ui.Modal, title="Geri Bildirim Formu"):
    konu = discord.ui.TextInput(
        label="Konu",
        placeholder="Kısaca konuyu yazın",
        max_length=50,
        required=True,
    )
    mesaj = discord.ui.TextInput(
        label="Mesajınız",
        style=discord.TextStyle.paragraph,
        placeholder="Düşüncelerinizi buraya yazın...",
        max_length=500,
        required=True,
    )

    async def on_submit(self, interaction: discord.Interaction):
        await interaction.response.send_message(
            f"✅ Teşekkürler {interaction.user.mention}!\n"
            f"**Konu:** {self.konu.value}\n**Mesaj:** {self.mesaj.value}",
            allowed_mentions=discord.AllowedMentions.none(),  # Yazılan metinle etiket atılmasın
            ephemeral=True,
        )


# ---------- Select Menu ----------
class RenkMenusu(discord.ui.Select):
    def __init__(self):
        secenekler = [
            discord.SelectOption(label="Kırmızı", value="kirmizi", description="Ateş gibi", emoji="🔴"),
            discord.SelectOption(label="Mavi", value="mavi", description="Deniz gibi", emoji="🔵"),
            discord.SelectOption(label="Yeşil", value="yesil", description="Orman gibi", emoji="🟢"),
        ]
        super().__init__(
            placeholder="Bir renk seçin...",
            min_values=1,
            max_values=1,
            options=secenekler,
            custom_id="panel:renk",
        )

    async def callback(self, interaction: discord.Interaction):
        await interaction.response.send_message(
            f"Seçtiğiniz renk: **{RENKLER[self.values[0]]}**", ephemeral=True
        )


# ---------- Butonlar + Menü (View) ----------
class PanelView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)  # Süresiz çalışsın
        self.sayac = 0
        self.add_item(RenkMenusu())

    @discord.ui.button(label="Selam Ver", style=discord.ButtonStyle.success, emoji="👋", custom_id="panel:selam")
    async def selam(self, interaction: discord.Interaction, button: discord.ui.Button):
        await interaction.response.send_message(f"Merhaba {interaction.user.mention}! 👋", ephemeral=True)

    @discord.ui.button(label="Tıklama: 0", style=discord.ButtonStyle.primary, custom_id="panel:sayac")
    async def sayac_butonu(self, interaction: discord.Interaction, button: discord.ui.Button):
        self.sayac += 1
        button.label = f"Tıklama: {self.sayac}"
        await interaction.response.edit_message(view=self)

    @discord.ui.button(label="Form Aç", style=discord.ButtonStyle.secondary, emoji="📝", custom_id="panel:form")
    async def form_ac(self, interaction: discord.Interaction, button: discord.ui.Button):
        await interaction.response.send_modal(GeriBildirimModal())


# ---------- Bot Olayları ve Komut ----------
@bot.event
async def setup_hook():
    bot.add_view(PanelView())   # Bot yeniden başlasa bile eski panellerin butonları çalışsın
    await bot.tree.sync()       # Slash komutları Discord'a kaydet


@bot.event
async def on_ready():
    print(f"Bot giriş yaptı: {bot.user}")


@bot.tree.command(name="panel", description="Buton, menü ve form içeren panel gönderir")
async def panel(interaction: discord.Interaction):
    await interaction.response.send_message(
        "🎛️ **SroCave Kontrol Paneli**\nAşağıdaki bileşenleri deneyin!",
        view=PanelView(),
    )


bot.run(os.getenv("TOKEN"))

Botu başlatmak için:
Bash:
python bot.py

📖 Kod Açıklaması
  • discord.ui.View: Butonları ve menüleri bir arada tutan sınıftır.
  • @discord.ui.button: Fonksiyonu bir butona çevirir, tıklanınca fonksiyon çalışır.
  • discord.ui.Select: Menü sınıfıdır. Seçim callback içinde self.values ile okunur.
  • discord.ui.Modal: Form sınıfıdır. Gönderilince on_submit çalışır.
  • timeout=None + custom_id: Bileşenleri kalıcı yapar. bot.add_view() ile bot yeniden başlasa bile eski panellerin butonları çalışmaya devam eder.
  • edit_message(): Tıklanan mesajı günceller (sayaç örneği).

📝 Not: bot.tree.sync() komutları global olarak kaydeder ve görünmesi biraz zaman alabilir. Hızlı test için sunucuya özel senkronizasyon kullanabilirsiniz. Sayaç değeri bot yeniden başlayınca sıfırlanır, kalıcı olması için veritabanı kullanmanız gerekir.



📏 Discord Sınırları (Çok Önemli!)​

  • Bir satırda en fazla 5 buton olabilir.
  • Bir mesajda en fazla 5 satır olabilir.
  • Bir satırda yalnızca 1 select menü olabilir (menü varsa o satıra buton eklenemez).
  • Select menüde en fazla 25 seçenek olabilir.
  • Modalda en fazla 5 metin alanı olabilir.
  • Modal başlığı ve alan etiketleri en fazla 45 karakter olabilir.
  • Custom ID en fazla 100 karakter olabilir ve mesaj içinde benzersiz olmalıdır.
  • Modal, etkileşime verilen ilk cevap olmalıdır. deferReply() sonrası açılamaz.
  • Bir modal gönderimine cevap olarak başka bir modal açılamaz.



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

  • Bu etkileşim başarısız oldu: Bot 3 saniye içinde cevap vermemiş veya custom ID'yi yakalayan kod yoktur. Custom ID'lerin birebir aynı olduğunu kontrol edin.
  • Unknown Interaction: Etkileşime iki kez cevap verilmiş veya süre dolmuştur.
  • Interaction has already been acknowledged: Aynı etkileşime hem reply hem update yapmışsınızdır. Birden fazla mesaj için followUp kullanın.
  • Restart sonrası butonlar çalışmıyor (discord.py): View timeout=None olmalı, her bileşenin custom_id değeri olmalı ve bot.add_view() çağrılmalıdır.
  • Invalid Form Body: Yukarıdaki sınırlardan birini aşmışsınızdır (çok fazla buton, uzun etiket gibi).

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



💡 Neler Yapabilirsiniz?​

  • Buton ile rol alma paneli
  • Buton ile doğrulama sistemi
  • Modal ile başvuru formu ve yetkili kanalına gönderim
  • Select menü ile ticket kategorisi seçtirme
  • Modal ile öneri / şikayet sistemi



🔗 Faydalı Kaynaklar​


🏁 Sonuç​

Buton, select menü ve modal; botunuzu komut ezberlemeden kullanılan, modern ve kullanıcı dostu bir yapıya dönüştürür. Bu üç bileşeni öğrendiğinizde ticket, doğrulama, rol alma ve başvuru gibi birçok sistemi kolayca yapabilirsiniz.

⚠️ 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