BenitzCoding
BenitzCoding

Reputation: 299

Async function as Decorator on Discord.py

I have a Discord.py Command, I want to make a custom permission handler.

My Command:

@commands.command()
@custom_permission("administrator")
async def example(self, ctx, args):
    ...

In this case, @custom_permission() is the Permission handler, now how do I make it work for a decorator that in async def?

The Decorator Function:

async def custom_permission(permission):
    
    async def predicate(ctx, permission):
        if ctx.author.id in config.owners:
            return True

        elif permission == "administrator":
            if ctx.author.guild_permissions.administrator:
                return True

            else:
                embed = discord.Embed(timestamp=ctx.message.created_at, description=f"You do not meet the required guild permissions the command \"`{ctx.command.name}`\" requires to be executed.\n\nYou need `{permission.upper()}` Permission in this Guild to be able to execute/run/use this command.", color=242424)
                embed.set_author(name="Insufficient Permissions", icon_url=config.forbidden_img)
                embed.set_footer(text="Numix", icon_url=config.logo)
                await ctx.send(embed=embed)

        elif permission == "manage_messages":
            if ctx.author.guild_permissions.manage_messages:
                return True

            else:
                embed = discord.Embed(timestamp=ctx.message.created_at, description=f"You do not meet the required guild permissions the command \"`{ctx.command.name}`\" requires to be executed.\n\nYou need `{permission.upper()}` Permission in this Guild to be able to execute/run/use this command.", color=242424)
                embed.set_author(name="Insufficient Permissions", icon_url=config.forbidden_img)
                embed.set_footer(text="Numix", icon_url=config.logo)
                await ctx.send(embed=embed)

        elif permission == "kick":
            if ctx.author.guild_permissions.kick:
                return True

            else:
                embed = discord.Embed(timestamp=ctx.message.created_at, description=f"You do not meet the required guild permissions the command \"`{ctx.command.name}`\" requires to be executed.\n\nYou need `{permission.upper()}` Permission in this Guild to be able to execute/run/use this command.", color=242424)
                embed.set_author(name="Insufficient Permissions", icon_url=config.forbidden_img)
                embed.set_footer(text="Numix", icon_url=config.logo)
                await ctx.send(embed=embed)

        elif permission == "ban":
            if ctx.author.guild_permissions.ban:
                return True
                
            else:
                embed = discord.Embed(timestamp=ctx.message.created_at, description=f"You do not meet the required guild permissions the command \"`{ctx.command.name}`\" requires to be executed.\n\nYou need `{permission.upper()}` Permission in this Guild to be able to execute/run/use this command.", color=242424)
                embed.set_author(name="Insufficient Permissions", icon_url=config.forbidden_img)
                embed.set_footer(text="Numix", icon_url=config.logo)
                await ctx.send(embed=embed)

        elif permission == "manage_guild":
            if ctx.author.guild_permissions.manage_guild:
                return True

            else:
                embed = discord.Embed(timestamp=ctx.message.created_at, description=f"You do not meet the required guild permissions the command \"`{ctx.command.name}`\" requires to be executed.\n\nYou need `{permission.upper()}` Permission in this Guild to be able to execute/run/use this command.", color=242424)
                embed.set_author(name="Insufficient Permissions", icon_url=config.forbidden_img)
                embed.set_footer(text="Numix", icon_url=config.logo)
                await ctx.send(embed=embed)
        

    return commands.check(predicate(ctx, permission))

Now, how do I make this work? I can't change it to a normal function, because If I did that, then I can't send the embed message when the permission requirement is met.

Upvotes: 0

Views: 501

Answers (2)

Łukasz Kwieciński
Łukasz Kwieciński

Reputation: 15728

You don’t need to make the outer-most function asynchronous, however the predicate function can be a coroutine

def custom_permission(permission):
    async def predicate(ctx):
        ...
    return commands.check(predicate)

Keep in mind that the predicate coroutine can only take one argument, ctx (the arguments are passed internally in the commands.check method, you do not pass them yourself) If you want to access the other arguments use ctx.args.

Upvotes: 1

decorator-factory
decorator-factory

Reputation: 3083

  1. Decorator syntax:
@decorator
def function():
    ...

is just syntax sugar for this:

def function():
    ...
function = decorator(function)
  1. An async function is a function that returns a coroutine.

In [1]: async def foo():
   ...:     return 42
   ...: 

In [2]: await foo()
Out[2]: 42

In [3]: foo()
Out[3]: <coroutine object foo at 0x7f7ca626da40>

So when you do

@an_async_function("argument")
async def foo():
    ...

an_async_function("argument") will be a coroutine object, which you are trying to call.

  1. commands.check expects an async function, while you're passing in the call to that function.

What you can do is:

a) use functools.partial to partially apply the predicate function to the permissions argument:

async def _check_permission(ctx, permission):
    ...

def custom_permission(permission):

    return commands.check(partial(_check_permission, permission=permission))

b) Just use the permission that you pass in the decorator inside predicate.

def custom_permission(permission):
    
    async def predicate(ctx):
        ...  # use `permission` here

    return commands.check(predicate)

Upvotes: 2

Related Questions