Zodra een Discord-bot begint te groeien, loopt één enkel main.py-bestand snel uit de hand — en precies daar komt de discord.py cog-structuur om de hoek kijken. Cogs zijn modules die gerelateerde commando's, event-listeners en toestand onder één klasse bundelen. Door elke functie in een eigen bestand te splitsen, geef je je bot een schone, leesbare en schaalbare architectuur. In deze gids bouwen we vanaf nul een modulair botskelet met discord.py 2.x.
Wat is een cog en waarom gebruik je er een?
Een cog is een Python-klasse die overerft van commands.Cog en commando's en listeners bevat. Terwijl de kern van de bot blijft draaien, kun je cogs tijdens runtime laden, ontladen of herladen. De praktische voordelen zijn:
- Scheiding van verantwoordelijkheden: elk domein — moderatie, muziek, economie — staat in een eigen bestand.
- Hot reloading: herlaad één module zonder de bot af te sluiten om wijzigingen te testen.
- Teamwerk: verschillende ontwikkelaars werken aan verschillende cogs zonder conflicten.
- Minder globale toestand: elke cog houdt zijn eigen toestand binnen de klasse.
Projectstructuur
De aanbevolen indeling houdt de kern in de root en plaatst alle cogs in een aparte map:
my-bot/
├── bot.py
├── cogs/
│ ├── moderation.py
│ ├── general.py
│ └── economy.py
├── requirements.txt
└── .env
Gebruik een virtuele omgeving om afhankelijkheden te installeren en zet ten minste discord.py en python-dotenv in je requirements.txt:
python -m venv .venv
source .venv/bin/activate
pip install -U discord.py python-dotenv
Het kernbestand: bot.py
In discord.py 2.x is de juiste plek om extensies te laden de methode setup_hook. Die wordt aangeroepen voordat de bot inlogt, en load_extension kan daar veilig worden ge-await. We breiden commands.Bot uit om een net startpunt te maken:
import os
import asyncio
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
INITIAL_EXTENSIONS = [
"cogs.general",
"cogs.moderation",
"cogs.economy",
]
class MyBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix="!", intents=intents)
async def setup_hook(self):
for ext in INITIAL_EXTENSIONS:
await self.load_extension(ext)
# Slash-commando's synchroniseren
await self.tree.sync()
async def on_ready(self):
print(f"Ingelogd als {self.user} (ID: {self.user.id})")
async def main():
bot = MyBot()
await bot.start(os.environ["DISCORD_TOKEN"])
if __name__ == "__main__":
asyncio.run(main())
Je moet ook de message_content-intent inschakelen in het Discord Developer Portal; anders werken prefix-commando's niet.
Je eerste cog schrijven
Elk cog-bestand heeft twee delen: een klasse die overerft van commands.Cog, en een asynchrone setup-functie onderaan het bestand. In discord.py 2.x is setup nu async en voegt de cog toe met await bot.add_cog():
import discord
from discord.ext import commands
class General(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="ping")
async def ping(self, ctx: commands.Context):
latency = round(self.bot.latency * 1000)
await ctx.send(f"Pong! {latency}ms")
@commands.Cog.listener()
async def on_member_join(self, member: discord.Member):
channel = member.guild.system_channel
if channel is not None:
await channel.send(f"Welkom {member.mention}!")
async def setup(bot: commands.Bot):
await bot.add_cog(General(bot))
Twee aandachtspunten: binnen een cog is de eerste parameter van elk commando altijd self, en listeners definieer je met de decorator @commands.Cog.listener() — nooit @bot.event.
Slash-commando's naar cogs verplaatsen
Moderne bots leunen steeds meer op slash-commando's. Je definieert ze binnen een cog met app_commands:
from discord import app_commands
from discord.ext import commands
import discord
class Economy(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@app_commands.command(name="balance", description="Toon je saldo")
async def balance(self, interaction: discord.Interaction):
await interaction.response.send_message(
"Je saldo: 1000 munten", ephemeral=True
)
async def setup(bot: commands.Bot):
await bot.add_cog(Economy(bot))
Slash-commando's moeten worden gesynchroniseerd om bij Discord geregistreerd te worden. Tijdens de ontwikkeling is synchroniseren naar een specifieke testserver (die direct verschijnt) veel sneller dan een globale sync; globale verspreiding kan tot een uur duren.
Cogs beheren tijdens runtime
Door een admin-cog toe te voegen, kun je modules laden, ontladen en herladen zonder de bot opnieuw te starten. Dat versnelt de ontwikkelcyclus aanzienlijk:
class Admin(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command()
@commands.is_owner()
async def reload(self, ctx, extension: str):
await self.bot.reload_extension(f"cogs.{extension}")
await ctx.send(f"`{extension}` herladen.")
async def setup(bot):
await bot.add_cog(Admin(bot))
De controle @commands.is_owner() beperkt deze gevoelige commando's tot alleen de eigenaar van de bot. Als een module een syntaxfout bevat, behoudt reload_extension de oude versie en werpt de fout op — het is dus een goede gewoonte om de exception op te vangen en aan de gebruiker te melden.
Veelgestelde vragen
Waarom moet de setup-functie async zijn?
Met discord.py 2.x is de levenscyclus van de bibliotheek volledig asynchroon geworden. Omdat add_cog en load_extension nu coroutines zijn, moet de setup-functie die ze aanroept ook async def zijn en intern await gebruiken.
Moet ik prefix-commando's of slash-commando's gebruiken?
Voor nieuwe projecten worden slash-commando's aanbevolen: ze zijn vindbaar in de Discord-interface, vullen automatisch aan en vereisen de message_content-intent niet. De cog-structuur herbergt beide moeiteloos in dezelfde klasse.
Als één cog kapotgaat, crasht dan de hele bot?
Nee. Als een cog tijdens load_extension niet laadt, wordt alleen die extensie overgeslagen; wikkel het in try/except en de andere cogs blijven prima laden. Tijdens runtime heeft elke cog een eigen scope voor foutafhandeling.
Wil je je bot migreren naar een nette cog-architectuur? Of je nu een bestaande bot in één bestand wilt omzetten naar een modulaire structuur, slash-commando's wilt toevoegen of een Discord-bot vanaf nul wilt bouwen, neem contact met me op.