Good practices¶
Loading multiple cogs¶
Since you would eventually have multiple cogs and cog files inside a folder, you definitely shouldn’t load each of them
separately with bot.load_extension()
. You’ll end up creating a giant chain that would look something like this.
bot.load_extension("cogs.hello")
bot.load_extension("cogs.ping")
bot.load_extension("cogs.pls")
bot.load_extension("cogs.help")
bot.load_extension("cogs.me")
You can easily create a for
loop that loads each cog in the folder. This will also eliminate the need of adding a new
bot.load_extension()
line each time you create a new cog file.
import os
for filename in os.listdir("./cogs"):
if filename.endswith(".py"):
client.load_extension(f"cogs.{filename[:-3]}")
Running code when a cog is loaded¶
Most people are used to running everything in __init__
but that doesn’t allow running async code. In this case you can
overwrite the special cog_load
method.
class MyCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
async def cog_load(self):
self.data = await bot.fetch_database_data()
@commands.slash_command()
async def command(self, interaction: disnake.ApplicationCommandInteraction, user: disnake.User):
await interaction.response.send_message(self.data[user.id])
Reloading your bot¶
A one of the lesser known disnake features is the reload
kwarg for your bot. It reloads extensions every time they are
edited; allowing you to test your code in real-time. This is especially useful if startup times for your bot are very
slow, since only one extension will be reloaded at a time.
from disnake.ext import commands
bot = commands.Bot(..., reload=True)
Warning
This should be used purely for debugging. Please do not use this in production.
Converting arguments in commands¶
discord.py used to have Converter
classes to convert arguments if they are provided. These were however very hard to
use with type-checkers because the type of the parameter is never actually the converter itself.
Disnake aims to eliminate this issue by only allowing conversion of builtin types like disnake.Member
,
disnake.Emoji
, etc. If you ever want to have your own converter you have to use the converter
argument in Param
.
async def convert_data(inter: disnake.ApplicationCommandInteraction, arg: str):
parts = arg.split(",")
return {"a": parts[0], "b": int(parts[1]), "c": parts[2].lower()}
@commands.slash_command()
async def command(
self,
inter: disnake.ApplicationCommandInteraction,
data: Dict[str, Any] = commands.Param(converter=convert_data),
):
...
class DataConverter(commands.Converter):
async def convert(self, ctx: commands.Context, arg: str):
parts = arg.split(",")
return {"a": parts[0], "b": int(parts[1]), "c": parts[2].lower()}
@commands.command()
async def command(self, ctx: commands.Context, data: DataConverter):
...
If you absolutely want to be able to use classes you may pass in a class method. Alternatively, set a method of the
class to be the converter using converter_method
.
from dataclasses import dataclass
@dataclass
class Data:
a: str
b: int
@classmethod
async def from_option(cls, inter: disnake.CommandInteraction, arg: str):
a, b = arg.split(",")
return cls(a, int(b))
@commands.slash_command()
async def command(
self,
inter: disnake.CommandInteraction,
data: Data = commands.Param(converter=Data.from_option),
):
...
from dataclasses import dataclass
@dataclass
class Data:
a: str
b: int
@commands.converter_method
async def from_option(cls, inter: disnake.CommandInteraction, arg: str):
a, b = arg.split(",")
return cls(a, int(b))
@commands.slash_command()
async def command(
self,
inter: disnake.CommandInteraction,
data: Data,
):
...
Context command targets¶
Instead of using inter.target
you should be using a parameter of your command.
@commands.user_command()
async def command(self, inter: disnake.ApplicationCommandInteraction, user: disnake.User):
await inter.response.send_message(f"Used on {user.mention}")
Slash command descriptions¶
You may use docstrings for command and option descriptions. Everything before Parameters
is the command description.
Everything after Parameters
are the option descriptions.
@commands.slash_command()
async def command(
self,
inter: disnake.ApplicationCommandInteraction,
category: str,
item: str,
details: bool,
):
"""Show item info
Parameters
----------
category: The category to search
item: The item to display
details: Whether to get the details of this time
"""
Guild-only commands¶
While disnake does provide a @commands.guild_only()
decorator, it still makes you handle guild
being optional in
case you’re using linters. To solve this you should be using GuildCommandInteraction
.
# before
@commands.slash_command()
@commands.guild_only()
async def command(inter: disnake.ApplicationCommandInteraction):
assert inter.guild is not None
await inter.send(inter.guild.name)
# after
@commands.slash_command()
async def command(inter: disnake.GuildCommandInteraction):
await inter.send(inter.guild.name)