# 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. ```python linenums="1" 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. ```python linenums="1" 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. ```python linenums="1" hl_lines="5-6" 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. ```python linenums="1" hl_lines="3" 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`. ````{tabbed} disnake ```python linenums="1" hl_lines="10" 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), ): ... ``` ```` ````{tabbed} discord.py ```python linenums="1" 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`. ````{tabbed} classmethod converter ```python linenums="1" hl_lines="9-12 19" 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), ): ... ``` ```` ````{tabbed} converter method ```python linenums="1" hl_lines="9-12 19" 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. ```python linenums="1" @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. ```python linenums="1" @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`. ```python linenums="1" # 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) ```