Skip to content

Instantly share code, notes, and snippets.

@cibere
Last active November 8, 2022 01:08
Show Gist options
  • Save cibere/9218fe05edc02a3b33d96125c23344de to your computer and use it in GitHub Desktop.
Save cibere/9218fe05edc02a3b33d96125c23344de to your computer and use it in GitHub Desktop.
Don't Auto Sync :D

Auto Syncing (your command tree) Sucks, and here's why:

Syncing your command tree automatically causes extra unnecessary requests to be made, this is because you only need to sync when commands are updated. *see when you should sync for a more enunciated list on when to sync.

What syncing does is send your commands to discord for one guild or globally. If you haven't changed your command's descriptions, added/removed commands, changed names, parameters, etc. you shouldn't sync, since you'd only be updating discord with the commands they already have without doing any changes, which is pointless and a waste of an API request to a limit with an already tight rate limit. *see what syncing is for a more enunciated on what syncing is, and how to do so.

Where should I sync instead? It's better to sync using a normal (message) command (or even an on_message if you prefer Client) You can even use just a simple eval to do so. *for example: Prebuilt sync command

*But I don't have the new message content intent... What now? Bots can still receive message content when the bot is mentioned in it, or in DMs! You could set the bot prefix to commands.when_mentioned

*Why do I get a Missing Access error when trying to sync to a guild? -> See Missing Access when syncing

When to sync

Sync when you...

  • Added/removed a command
  • Changed a command's...
    • name (name= kwarg or function name)
    • description (description= kwarg or docstring)
  • Added/removed an argument
  • Changed an argument's...
    • name (rename decorator)
    • description (describe decorator)
    • type (arg: str str is the type here)
  • Added/modified permissions:
    • guild_only decorator or kwarg
    • default_permissions decorator or kwarg
    • nsfw kwarg
  • Converted the global/guild command to a guild/global command

Do not sync when you...

  • Changed anything in the function's body (after the async def (): part)
  • Added or modified a library side check:
    • (@)app_commands.checks...
    • (@)commands...(.check)
    • @app_commands.checks.(dynamic_)cooldown(...)

This is the same for hybrid commands

What syncing is

App commands work differently from message commands, they're handled mostly on Discord's end. Discord just tells your bot when someone successfully triggers a command. In order to do this, you need to register your commands on the command tree then tell discord they exist by syncing with tree.sync. Commands can be registered on the tree either as a global command or as a guild-specific command, and must be synced to the same scope they are associated with in the tree. When you sync, you are telling Discord about the commands you currently have for a particular scope.

To sync global commands: await tree.sync() To sync guild commands: await tree.sync(guild=guild) Guilds must be either a Guild object or a discord.Object with the guild's id.

It may help to think of the tree as a dict like this.

{
  None: [global, commands],
  guild_one: [guild, one, commands],
  guild_two: [guild, two, commands]
}

copy_global_to will copy [global, commands] and add them to the commands for the guild you pass. This is only done locally, you must still sync.

All commands are global by default. There are a few ways to make them guild-specific:

  • @app_commands.guilds() decorator on an app_commands.Group sublcass or @app_commands.command()
  • guild/guilds in @tree.command()
  • guild/guilds in bot.add_cog, only if the cog is a GroupCog
  • guild/guilds in tree.add_command, not typically used

A common practice for syncing is to pick a specific guild for testing and run tree.copy_global_to(guild=guild) then tree.sync(guild=guild). When you're done testing, tree.clear_commands(guild=guild) then tree.sync(guild=guild). When you're ready to publish your commands, tree.sync(). The Prebuilt sync command makes this flow easy using !sync \*, !sync ^, and !sync, respectively.

when you should sync a prebuilt sync command

Command Tree Explained

When you declare an app command and register it in your CommandTree, you do so under a certain scope: global (by default) or one (or more) guilds. The CommandTree stores a mapping of scope : list of commands.

When you call CommandTree.sync, you sync one scope of the CommandTree, either the list of global commands (sync()) or the list for one guild (sync(guild=discord.Object(...))).

Calling tree.sync() does not sync all of your commands globally, just the ones registered as global. Similarly, tree.sync(guild=...) does not sync all of your commands to that guild, just the ones registered to that guild.

copy_global_to will copy the global list of commands in your tree into the list of commands for the specified guild. This is not permanent (if you don't do it again when your bot restarts) and it does not impact the state of your commands on discord (you still have to sync).

Prebuilt sync command

@bot.command()
@commands.guild_only()
@commands.is_owner()
async def sync(
  ctx: Context, guilds: Greedy[discord.Object], spec: Optional[Literal["~", "*", "^"]] = None) -> None:
    if not guilds:
        if spec == "~":
            synced = await ctx.bot.tree.sync(guild=ctx.guild)
        elif spec == "*":
            ctx.bot.tree.copy_global_to(guild=ctx.guild)
            synced = await ctx.bot.tree.sync(guild=ctx.guild)
        elif spec == "^":
            ctx.bot.tree.clear_commands(guild=ctx.guild)
            await ctx.bot.tree.sync(guild=ctx.guild)
            synced = []
        else:
            synced = await ctx.bot.tree.sync()

        await ctx.send(
            f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}"
        )
        return

    ret = 0
    for guild in guilds:
        try:
            await ctx.bot.tree.sync(guild=guild)
        except discord.HTTPException:
            pass
        else:
            ret += 1

    await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.")

Greedy -> commands.Greedy Context -> commands.Context (or your subclass) Object -> discord.Object typing.Optional and typing.Literal

Works like: !sync -> global sync
!sync ~ -> sync current guild
!sync * -> copies all global app commands to current guild and syncs
!sync ^ -> clears all commands from the current guild target and syncs (removes guild commands)
!sync id_1 id_2 -> syncs guilds with id 1 and 2

Missing Access when syncing

Why do i get MISSING ACCESS when trying to sync my tree to a guild? The bot does not have the applications.commands scope. All you have to do is go to https://discord.com/developers/applications/%3E and follow steps shown below: https://media.discordapp.net/attachments/381965515721146390/957385902735364186/unknown.png?width=1440&height=515 You do not have to kick your bot from the guild, just re-invite it with the generated URL.

Credits

reasons on why auto syncing sucks - LeoCx1000#9999

when to sync - Soheab_#6240

what syncing is - sgtlaggy#5516

command tree explination - SolsticeShard#6320

prebuilt sync command - Umbra#0009

missing access when syncing - LeoCx1000#9999

All of these discord users are from the official discord.py server: https://discord.gg/dpy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment