1

I am trying to implement slash commands on discord using discord.py through steps shown in this video

The code in the video (which works) is as follows:

import discord
from discord import app_commands

class aClient(discord.Client):
    def __init__(self):
        super().__init__(intents=discord.Intents.default())
        self.synced = False
    async def on_ready(self):
        await self.wait_until_ready()
        if not self.synced:
            await tree.sync(guild=discord.Object(id=GUILD_ID))
            self.synced = True
        printf(f"logged in as {self.user}")

client = aClient()
tree = app_commands.CommandTree(client)

@tree.command(name="test", description="testing", guild=discord.Object(id=GUILD_ID))
async def slash(interaction: discord.Interaction, name: str):
    await interaction.response.send_message("test")
    
client.run(TOKEN)

But I would like to incorporate the outside instantiation and decoration of the tree object and the slash function into the main class itself, and not make the tree object separate from the main class and also the slash function part of the class. I read this answer and tried the following:

import discord
from discord import app_commands

class aClient(discord.Client):
    def __init__(self):
        super().__init__(intents=discord.Intents(messages=True, message_content=True))
        self.tree = app_commands.CommandTree(self)
        self.slash = self.tree.command(self.tree, name="name", description="name", guild=discord.Object(id=GUILD_ID))
        self.synced = False
    async def on_ready(self):
        print(f"Connected as {self.user}")
        await self.wait_until_ready()
        if not self.synced:
            self.tree.sync(guild=discord.Object(id=GUILD_ID))
            self.synced = True
    
    async def slash(self, interaction: discord.Interaction, name: str):
        await interaction.response.send_message("test")

client = aClient()
client.run(TOKEN)

But doing so gives the following error:

Traceback (most recent call last):                                                                                                                   
  File "E:\myprojects\new.py", line 33, in <module>                                                                                                  
    client = aClient()                                                                                                                               
  File "E:\myprojects\new.py", line 15, in __init__                                                                                                  
    self.slash = self.tree.command(self.tree, name="name", description="name", guild=discord.Object(id=GUILD_ID))                         
TypeError: CommandTree.command() takes 1 positional argument but 2 positional arguments (and 3 keyword-only arguments) were given 

I have used environment variables for GUILD_ID etc

1 Answers1

1

The core idea is how does @ syntax translates to function call

@decorator
def my_func():
    #Do stuff

translates to:

my_func = decorator(my_func)

But this is not how decorator that has arguments work

@decorator_with_args(args)
def my_func():
    #Do stuff

The above translates to :

my_func = decorator_with_args(args)(my_func)

as said [here][1], this means that the decorator_with_args is not the actual decorator afterall, but instead returns the decorator. It can be further seen in the definition of the command function in tree.py under app_commands:

def command(
        self,
        *,
        name: str = MISSING,
        description: str = MISSING,
        nsfw: bool = False,
        guild: Optional[Snowflake] = MISSING,
        guilds: Sequence[Snowflake] = MISSING,
        extras: Dict[Any, Any] = MISSING,
    )

As can be seen above, the function above doesnt have an argument for another function. The actual decorator is the inner function defined inside command and rightly named as "decorator":

def decorator(func: CommandCallback[Group, P, T]) -> Command[Group, P, T]:

Depending on the arguments we give to the decorator_with_args() it returns a custom decorator that actually takes in a function.

So the code that works is:

import discord
from discord import app_commands

class aClient(discord.Client):
    
    def __init__(self):
        super().__init__(intents=discord.Intents(messages=True, message_content=True))
        self.synced = False
        self.tree = app_commands.CommandTree(self)
        self.slash = self.tree.command(name="name", description="name", guild=discord.Object(id=GUILD_ID))(self.slash)
    async def on_ready(self):
        print(f"Connected as {self.user}")
        await self.wait_until_ready()
        if not self.synced:
            await self.tree.sync(guild=discord.Object(id=GUILD_ID))
            self.synced = True
    async def slash(self, interaction: discord.Interaction, name: str):
        await interaction.response.send_message("test")

client = aClient()
client.run(TOKEN)

I know this doesnt achieve any new functionality, but aleast this serves as a means to enhance conceptual understanding [1]: https://stackoverflow.com/a/25827070/16301435