Kendi discordpy müzik botu projeni sıfırdan kurmak, Python öğrenmenin en keyifli yollarından biri. Bu rehberde discord.py kütüphanesini, ses kaynağını çözen yt-dlp aracını ve sesi Discord'a aktaran FFmpeg'i bir araya getirip slash komutlarıyla çalan, kuyruk yöneten bir bot yapacağız. Amaç sadece "çalışan" değil, anlaşılır ve genişletilebilir bir temel kurmak.
Botu çalıştırmadan önce gerekenler
Başlamadan önce ortamı hazırlayalım. Modern bir Python sürümü (3.9 veya üzeri önerilir), bir Discord uygulaması/bot token'ı ve sistemde kurulu FFmpeg gereklidir. FFmpeg ayrı bir programdır; pip ile değil, işletim sisteminin paket yöneticisiyle kurulur (Linux'ta apt install ffmpeg, macOS'te brew install ffmpeg, Windows'ta resmi derlemeyi indirip PATH'e ekleyerek).
Discord tarafında Discord Developer Portal üzerinden bir uygulama oluştur, "Bot" sekmesinden token'ı al ve Message Content Intent ile sesli kanallara katılma izinlerini ver. Token'ı koda gömme; bir .env dosyasında sakla.
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -U discord.py yt-dlp python-dotenv
pip install -U "discord.py[voice]"
discord.py[voice] ekstrasını kurmak, sesli bağlantı için gereken PyNaCl bağımlılığını da getirir. Bu olmadan bot sesli kanala bağlanamaz.
İskelet: bot ve slash komutları
Modern botlar metin komutları (!play) yerine slash komutları kullanır. discord.py bunları app_commands ile sunar. Aşağıdaki iskelet token'ı ortamdan okur ve komutları senkronize eder.
import os
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv()
intents = discord.Intents.default()
intents.message_content = True
class MusicBot(discord.Client):
def __init__(self):
super().__init__(intents=intents)
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
await self.tree.sync()
bot = MusicBot()
@bot.event
async def on_ready():
print(f"Giriş yapıldı: {bot.user}")
bot.run(os.getenv("DISCORD_TOKEN"))
yt-dlp ile ses kaynağını çözmek
Bir bağlantıyı sesli akışa dönüştürmek için yt-dlp'yi kullanırız. Tüm dosyayı indirmek yerine doğrudan akış URL'sini alırız; bu daha hızlıdır ve disk kullanmaz. Aşağıdaki yardımcı sınıf, bir arama metni veya URL'den oynatılabilir bir FFmpegOpusAudio kaynağı üretir.
import asyncio
import yt_dlp
YTDL_OPTS = {
"format": "bestaudio/best",
"noplaylist": True,
"quiet": True,
"default_search": "ytsearch",
"source_address": "0.0.0.0",
}
FFMPEG_OPTS = {
"before_options": "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5",
"options": "-vn",
}
ytdl = yt_dlp.YoutubeDL(YTDL_OPTS)
class Track:
def __init__(self, data):
self.title = data.get("title")
self.url = data.get("url")
@classmethod
async def from_query(cls, query):
loop = asyncio.get_event_loop()
data = await loop.run_in_executor(
None, lambda: ytdl.extract_info(query, download=False)
)
if "entries" in data:
data = data["entries"][0]
return cls(data)
Burada iki nokta önemli: extract_info bloklayıcı (senkron) bir çağrıdır, bu yüzden run_in_executor ile ayrı bir thread'de çalıştırırız; aksi halde botun olay döngüsü donar. Ayrıca before_options içindeki -reconnect bayrakları, uzun parçalarda akış koparsa yeniden bağlanmayı sağlar.
Kuyruk yönetimi
İyi bir müzik botunun kalbi kuyruktur. Her sunucu (guild) için ayrı bir kuyruk tutmak gerekir, çünkü botun aynı anda birden çok sunucuda çalması olağandır. Basit bir yapı için sunucu kimliğini bir sözlükte tutan kuyruklar kullanabiliriz.
from collections import deque
queues = {} # guild_id -> deque[Track]
def get_queue(guild_id):
if guild_id not in queues:
queues[guild_id] = deque()
return queues[guild_id]
def play_next(voice_client, guild_id):
queue = get_queue(guild_id)
if not queue:
return
track = queue.popleft()
source = discord.FFmpegOpusAudio(track.url, **FFMPEG_OPTS)
voice_client.play(
source,
after=lambda e: play_next(voice_client, guild_id),
)
play metodunun after geri çağırması, parça bitince otomatik olarak sıradakini başlatır. Dikkat: bu geri çağırma ayrı bir thread'de çalışır, bu yüzden içinde doğrudan await kullanamazsın. Asenkron iş yapman gerekirse bot.loop.call_soon_threadsafe veya asyncio.run_coroutine_threadsafe ile olay döngüsüne geri dönmelisin.
Komutları bağlamak: katıl, çal, atla
Artık parçaları slash komutlarına bağlayabiliriz. /play komutu kullanıcının bulunduğu sesli kanala katılır, kaynağı çözer, kuyruğa ekler ve bir şey çalmıyorsa oynatmayı başlatır.
@bot.tree.command(name="play", description="Bir şarkı çal")
async def play(interaction: discord.Interaction, sorgu: str):
await interaction.response.defer()
if not interaction.user.voice:
await interaction.followup.send("Önce bir ses kanalına gir.")
return
channel = interaction.user.voice.channel
vc = interaction.guild.voice_client
if vc is None:
vc = await channel.connect()
track = await Track.from_query(sorgu)
get_queue(interaction.guild.id).append(track)
await interaction.followup.send(f"Kuyruğa eklendi: {track.title}")
if not vc.is_playing():
play_next(vc, interaction.guild.id)
@bot.tree.command(name="skip", description="Sıradaki şarkıya geç")
async def skip(interaction: discord.Interaction):
vc = interaction.guild.voice_client
if vc and vc.is_playing():
vc.stop() # 'after' geri çağırması sıradakini başlatır
await interaction.response.send_message("Atlandı.")
else:
await interaction.response.send_message("Çalan bir şey yok.")
interaction.response.defer() çağrısı kritiktir: yt-dlp çözümü birkaç saniye sürebilir ve Discord, yanıt verilmeyen etkileşimleri 3 saniyede zaman aşımına uğratır. defer ile "düşünüyor" durumu gösterip ardından followup ile gerçek yanıtı yollarız.
Yayına almadan önce dikkat edilecekler
Bot çalıştığında işin bitmiş sayılmaz. Birkaç pratik nokta:
- Hata yönetimi: kaynak bulunamazsa veya yaş kısıtlaması varsa
yt-dlpistisna fırlatır; komutlarıtry/exceptile sar. - Boş kanal kontrolü: kimse kalmadığında botu otomatik olarak ayır ki gereksiz kaynak harcamasın.
- Telif ve şartlar: içerik kaynaklarının kullanım koşullarına saygı göster; yalnızca izin verilen içeriği çal.
- Barındırma: 7/24 çalışması için botu küçük bir VPS'te ya da bir konteynerde çalıştırmak en güvenilir yöntemdir.
Sık Sorulan Sorular
Neden bot bağlanıyor ama ses gelmiyor?
En sık neden FFmpeg'in kurulu olmaması ya da PATH'te bulunmamasıdır. Terminalde ffmpeg -version komutunu çalıştırıp doğrula. Ayrıca PyNaCl kurulu değilse ses akışı çalışmaz; pip install "discord.py[voice]" ile çözülür.
Slash komutları görünmüyor, ne yapmalıyım?
Global komut senkronizasyonu Discord'da yayılması bir saate kadar sürebilir. Geliştirme sırasında komutları tek bir sunucuya (guild) senkronlamak anında görünür olmalarını sağlar; bunun için tree.sync(guild=discord.Object(id=...)) kullan.
discord.py mı yoksa başka kütüphane mi?
Python tarafında discord.py en olgun ve en iyi belgelenmiş seçenektir; slash komutları, ses ve modern API özelliklerini tam destekler. JavaScript tercih edersen discord.js benzer bir yol sunar, ama bu rehberin tüm örnekleri Python içindir.
Botunu bir üst seviyeye taşımak mı istiyorsun? Filtreler, çalma listeleri, web paneli veya çoklu sunucu desteği gibi özellikler eklemek için birlikte çalışabiliriz. Projeni konuşmak için benimle iletişime geç.