How do I know if a command requires the user to have a certain permission (to function)?

Viewed 104

How do I go about retrieving a list of commands (Commands) that require the user to have the 'administrator' permission (to use them)? I am making a 'help' command; I would like to be able to retrieve all commands that require a certain permission in a list.

For example:

class Moderate(commands.Cog):
      @commands.command()
      @has_permissions(administrator=True)
      async def kick(ctx, member):
          #kick member

cog = Moderate
print(cog.commands.checks) -> {"kick" : administrator=True} #How to get this or the commands my bot has with this permission requirement?
3 Answers

My solution to this problem is to read the file and get the commands' names from it by checking if there's a "has_permissions(" string before it:

import os
file_path = os.path.abspath(__file__) #Get the file path of this file
self = open(file_path, "r", encoding="utf-8") #Specify the encoding as it's a Python file
code = self.readlines()
commands = [] #This is the list of all the commands your bot has
for line_number, line in enumerate(code):
    if "has_permissions(administrator" in line: #Check if the decorator ("@has_permissions(administrator)") is in the line
        command = code[line_number + 1] #Get the line after the decorator's
        command_name = command.replace("async def ", "").strip()[:-1] #Removing "async def ", colon and extra spaces
        if "command = code[line_number + " not in command_name: #Adding all commands' names except this code we're using
            commands.append(command_name)
            
print(commands) #See the 100 commands your bot has

This will not work if you check if the user has the 'administrator' permission in a way other than the @has_permissions(administrator=True) decorator. For example, if you have a 'ban' command like this, it won't be added to the list:

bot.command()
async def ban(message, member:discord.Member):
    if message.author.guild_permissions.administrator: #The method won't work if you check using this
        #Ban the member

This also won't work if you have extra blank lines between the decorator and the command's definition etc.

However, you can modify this to meet your requirements.

Here's a less hacky way of doing it that I personally prefer:

We can use a decorator to set a special attribute in the Command object, which we can then look at later. (Note: it is important that the @special decorator is placed before the @client.command. It needs to work off the Command object, not the actual callable function itself.)

def special(command):
    command.__special_flag__ = None  # the value is irrelevant, you just need to set it
    return command

@special
@client.command()
async def special_command(ctx):
    await ctx.send('hi')

@special
@client.command()
async def list_special_commands(ctx):
    for command in client.commands:
        if hasattr(command, '__special_flag__'):  # this checks if "__special_flag__" is set
            await ctx.send(f'special command! {command}')
    await ctx.send('ok')

You can then iterate through every command in the client and check if that flag is set. You can then add this extra decorator to any command you want to check permissions for. Then, in your help command, only display commands with this flag set.

Output looks like this:

enter image description here


As @The Amateur Coder mentioned, this might take a bit of time to add them all, so you can make this combined decorator that does all of it:

def combine_stuff(function):
    function = commands.has_permissions(...)(function)
    command = client.command(...)(function)
    command = special(command)

Explanation

There exists a very simple way to see which commands require administrator permissions. All command checks' predicates are in closures, and for has_permissions, one of the nonlocal variables of these functions will be the specified permissions.

This code must be run after all the commands are defined. For example, it can be run in on_ready, or before bot.run. You do not need to modify any other functions to run this code.

Code

@bot.event
async def on_ready():
    command_list = []
    for command in bot.walk_commands():
        for check in command.checks:
            closure = check.__closure__
            if closure is None:
                continue
            # loop through variables used by the check
            for cell in closure:
                # if administrator=True was passed
                if cell.cell_contents.get("administrator"):
                    command_list.append(command)
                    break
    print(command_list)

Reference

How do I access/modify variables from a function's closure?

Definition of has_permissions

Related