This seems to be a common "gotcha" with users new to the library. As such I have been asked to cover it a bit more here.
CommandTree.sync is how we make Discord aware of our command definitions. This means that we use an API request to send them a copy of what our commands look like and act like, so they can display it to your users in the client.
If you do not sync your tree, the commands will not show up, or update them if you make changes locally.
I cover some more items relating to this in the next file below. But for now let's cover the basics and what you can do here.
Common caveats
In my time as Helper, I see people syncing their CommandTree in the on_ready_event or in the new setup_hook entrypoint method.
I do not advise this personally, it can lead to footguns if you aren't prepared.
For examples, if you sync your tree before you load your extensions (which have your application commands), then you're effectively syncing an empty tree to Discord, which will wipe your commands.
If you sync your tree and then make changes, you rely on the autosync and forget to sync changes, resulting in errors and failing commands.
This is why it is strongly recommended to sync on demand with a command (ideally with a message command) and know when to do such things. I cover that later too.
What I've been recommending to people, is to maintain a sync command as a Message Command (@bot.command()) or some sort of message invoke.
Bots without the message content intent will still receive content of messages that mention them or are in DMs.
It's not like you can use a slash command to sync... when that command isn't synced.
Syncing gotchas
There is a ratelimit on syncing global commands which add commands.
Updating commands (currently) has no ratelimit.
It is still recommended to sync your commands to a test guild before syncing them globally. Discord.py has added a helper method to do just that:
CommandTree.copy_global_to
This util is used as follows:
# you have a defined Bot, with a tree and have commands added to it that are global.guild=ctx.guildordiscord.Object(id=...) # you can use a full discord.Guild as the method accepts a SnowflakeBot.tree.copy_global_to(guild=guild)
All this method does is copy your defined global commands (so ones without a guild or guilds kwarg, or without the @app_commands.guilds() decorator)
to the specified guild within the CommandTree.
When you use this method you must sync afterward still, you can refer to when_to_sync.md for details there.
Sync command example
fromtypingimportLiteral, Optionalfromdiscord.extimportcommandsfromdiscord.ext.commandsimportGreedy, Context# or a subclass of yours@bot.command()@commands.guild_only()@commands.is_owner()asyncdefsync(
ctx: Context, guilds: Greedy[discord.Object], spec: Optional[Literal["~", "*", "^"]] =None) ->None:
ifnotguilds:
ifspec=="~":
synced=awaitctx.bot.tree.sync(guild=ctx.guild)
elifspec=="*":
ctx.bot.tree.copy_global_to(guild=ctx.guild)
synced=awaitctx.bot.tree.sync(guild=ctx.guild)
elifspec=="^":
ctx.bot.tree.clear_commands(guild=ctx.guild)
awaitctx.bot.tree.sync(guild=ctx.guild)
synced= []
else:
synced=awaitctx.bot.tree.sync()
awaitctx.send(
f"Synced {len(synced)} commands {'globally'ifspecisNoneelse'to the current guild.'}"
)
returnret=0forguildinguilds:
try:
awaitctx.bot.tree.sync(guild=guild)
exceptdiscord.HTTPException:
passelse:
ret+=1awaitctx.send(f"Synced the tree to {ret}/{len(guilds)}.")
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sorry that was a typo, it is a : I also tried with guilds: commands.Greedy[discord.Guild] but same effect, I use it in a @app_commands.command() in the cog maybe that is why? Is it only reserved to old bot.command ?
Thanks a lot for the information.
Altho I have another question sorry to bother but I cant any information anywhere about it,
I have an app command using my models from db to load items for a converter (event: Enum('Events', Event.get_event_dict_enum_from_db())).
And I have another command to add new entry in the db. I dont use bot.tree.copy_global_to (should I?) I also dont use bot.tree.sync anywhere (yet).
The issue I am facing is when I add a new entry for the converter in the db, and after issuing the !sync or !sync ~ command, the list is not updated. I tried to put a .sync after the entry is added in the adding command but without success too.
How would you tackle this? Is it even possible? (the command converter list get updated after a reboot of the bot and a sync) - the bot is intended to be used only within one guild
- # this a app command group+ # this is an App command group- # you can nest these up 1: - # `/group group` = VALID+ # Valid combinations:+ # /group subcommand (up to 25)+ # /group subcommand group+ # /group subcommand group subcommand (up to 25)- # /group group group` = INVALID- # etc... all invalid.# this example shows one way to do this, subclassing# the other is constructing an instance of app_commands.Group()# that one is shown in "free_function_commands-py"
import discord
from discord import app_commands
- # the @app_commands.guilds and others (including checks) can be used above the class+ # checks like @app_commands.guilds and @app_commands.default_permissions can be used above the class- # these will apply to ALL subcommand, subcommands cannot have invidual perms!+ # these will apply to all subcommands, subcommands cannot have individual perms.
@app_commands.guild_only()
class Group(app_commands.Group):
- # this a app command group+ # this is an App command group- # you can nest these up 1: - # `/group group` = VALID+ # Valid combinations:+ # /group subcommand (up to 25)+ # /group subcommand group+ # /group subcommand group subcommand (up to 25)- # /group group group` = INVALID- # etc... all invalid.# this example shows one way to do this, subclassing# the other is constructing an instance of app_commands.Group()# that one is shown in "free_function_commands-py"
import discord
from discord import app_commands
- # the @app_commands.guilds and others (including checks) can be used above the class+ # checks like @app_commands.guilds and @app_commands.default_permissions can be used above the class- # these will apply to ALL subcommand, subcommands cannot have invidual perms!+ # these will apply to all subcommands, subcommands cannot have individual perms.
@app_commands.guild_only()
class Group(app_commands.Group):
I know this is old, but do you have anything to add an input in the command, like /ban user:<user> reason:[reason] with <> being required and [] is optional?
I know this is old, but do you have anything to add an input in the command, like /ban user:<user> reason:[reason] with <> being required and [] is optional?
You can give it a default value of something like None and check that in your function accordingly.
I know this is old, but do you have anything to add an input in the command, like /ban user:<user> reason:[reason] with <> being required and [] is optional?
You can give it a default value of something like None and check that in your function accordingly.
Im actually having trouble with this too haha! How do you read in "the rest of the message" my old way was to just
*args
But now i cant do that because of typing, *args: tuple doesn't work, nor does str, I'm at my wits end!
The reason for this is not discord.py related. Slash commands themselves have no concept of "consumption args" or varargs like ext.commands implements.
You simply cannot do this this way.
The recommend way is to take a str input and then .split() it with a clear separator, or have N many arguments.
Sad but this is the most recommended ways of doing it.
The reason for this is not discord.py related. Slash commands themselves have no concept of "consumption args" or varargs like ext.commands implements.
You simply cannot do this this way.
The recommend way is to take a str input and then .split() it with a clear separator, or have N many arguments. Sad but this is the most recommended ways of doing it.
That makes it really hard for users who don't usually have concepts of wrapping strings with " or using other deliminaters than space :c i might just not migrate these commands that expect full sentence strings until something can be figured out.
It is an annotation, not an assignment.
You have
=
, you should have: