3
votes

So I am setting up a bot, and I am testing it out. A little function I have there is to kick, ban and unban users, set up in a cog, which goes as follows:

import discord
from discord.ext import commands

class Moderator(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

#KICK command
@commands.command()
@commands.has_permissions(kick_members=True)
async def kick(self, ctx, member : discord.Member, *, reason=None):
    ('About to kick')
    await member.kick(reason = reason)

@commands.command()
@commands.has_permissions(kick_members=False)
async def kick(self, ctx, member : discord.Member, *, reason=None):
    await ctx.send(f'You do not have permission to kick any member, {ctx.message.author.mention}!')

#BAN command
@commands.command()
@commands.has_permissions(ban_members=True)
async def ban(self, ctx, member : discord.Member, *, reason=None):
    await member.ban(reason = reason)
    await ctx.send(f'Banned {member.mention}')

@commands.command()
@commands.has_permissions(kick_members=False)
async def ban(self, ctx, member : discord.Member, *, reason=None):
    await ctx.send(f'You do not have permission to ban any member, {ctx.message.author.mention}!')

#UNBAN command
@commands.command()
@commands.has_permissions(ban_members=True)
async def unban(self, ctx, *, member):
    banned_users = await ctx.guild.bans()
    member_name, member_discriminator = member.split('#')

    for ban_entry in banned_users:
        user = ban_entry.user

        if (user.name, user.discriminator) == (member_name, member_discriminator):
            await ctx.guild.unban(user)
            await ctx.send(f'Unbanned {user.mention}')
            return

@commands.command()
@commands.has_permissions(kick_members=False)
async def unban(self, ctx, member : discord.Member, *, reason=None):
    await ctx.send(f'You do not have permission to unban any member, {ctx.message.author.mention}!')


#CLEAR MESSAGES
@commands.command()
@commands.has_permissions(manage_messages=True)
async def clear(self, ctx, amount=2):
    await ctx.channel.purge(limit=amount)

@commands.command()
@commands.has_permissions(manage_messages=False)
async def clear(self, ctx, amount=2):
    await ctx.send(f'You do not have permission to delete messages in this way, {ctx.message.author.mention}!')

def setup(bot):
    bot.add_cog(Moderator(bot))

Now I have formatted the above code with some spaces so that it fits in one codeblock, so you might face indentation errors if you copy paste it elsewhere.

Moving on, the bot itself has Administrator rights and separate Kicking and Banning rights too. It is also placed at the top of the role hierarchy, which is seen as:

Role Hierarchy

The name of my bot is JunkBot.

Now, whenever I, being the server owner, try using the command .kick @user, it pops up the following error:

Error upon using kick command

The textual form of the error is:

Ignoring exception in command kick:
Traceback (most recent call last):
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\bot.py", line 939, in invoke
    await ctx.command.invoke(ctx)
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 855, in invoke
    await self.prepare(ctx)
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 777, in prepare
    if not await self.can_run(ctx):
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 1087, in can_run
    return await discord.utils.async_all(predicate(ctx) for predicate in predicates)
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\utils.py", line 348, in async_all
    for elem in gen:
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 1087, in <genexpr>
    return await discord.utils.async_all(predicate(ctx) for predicate in predicates)
  File "C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\commands\core.py", line 1790, in predicate
    raise MissingPermissions(missing)
discord.ext.commands.errors.MissingPermissions: You are missing Kick Members permission(s) to run this command.

Similar errors are raised for the ban, unban and clear messages commands.

Interesting part is, I, being the owner, get this error, but supposing another user, a friend of mine who does not have the kicking, banning or message managing role, runs their lines of codes perfectly, where she gets the message from the bot that she does not have permission to kick, ban or clear messages. The screenshot is enclosed.

I don't know where I went wrong. Please help me debug it.

2
Your kick command works for me as written (as the owner of my own server) - Timothy G.
Are you sure you used the exact same code as mine? Was your bot hierarchy somewhat similar to mine? Cam you share those details, like what permissions you gave to your bot and what roles did you as the owner have and the hierarchy of the roles? - junk1ord
My bot has admin permissions, and I copied and pasted your code (the Moderator class and the kick command) fixing the indents as necessary. - Timothy G.
Just to clarify, you, the owner of the server (with all perms) cannot call this command. But, someone who doesn't have any perms, can use the commands? (I mean use as in the bot does actually do what its expected to do) - 12944qwerty
Right, I am the owner, with all the perms and I can't call this command, but if someone WITHOUT the perms calls this command, the bot responds the way it is supposed to. - junk1ord

2 Answers

2
votes

Your problem is in rewriting the functions. I'm assuming that you are hoping to make an errorhandler. However, this isn't the way to do so.

Instead of rewriting the functions with different has_permissions arguments, you will need to make a proper version of an error handler.

First, delete all the duplicate functions. These are the functions that require False on the has_permissions arguments.

Next, make a errorhandler function. You can make one for the Cog commands only, for all commands, or a specific command. If its for all commands, you will use the event on_command_error, (I suggest using it in the main file or as a listener). If it's for the Cog, then you will have to use cog_command_error. If it's for a specific command, you should make the Command.error decorator.
I will be showing the Cog specific handler, but switching back and forth shouldn't take too long.

# Indent into Cog level
async def cog_command_error(self, ctx, error):
    # Allows us to check for original exceptions raised and sent to CommandInvokeError.
    # If nothing is found. We keep the exception passed to on_command_error.
    error = getattr(error, 'original', error)
   
    if isinstance(error, commands.BotMissingPermissions): # Check if bot is missing permissions
        await ctx.send(f'I am missing these permissions to do this command:\n{self.lts(error.missing_perms)}')

    elif isinstance(error, commands.MissingPermissions): # Check if user is missing permissions
        await ctx.send(f'You are missing these permissions to do this command:\n{self.lts(error.missing_perms)}')


@staticmethod
def lts(list_: list): # Used to make reading the list of permissions easier.
    """List to string.
        For use in `self.on_command_error`"""
    return ', '.join([obj.name if isinstance(obj, discord.Role) else str(obj).replace('_', ' ') for obj in list_])

Here is the list of exceptions you can use in the handler: Exceptions

FYI, the lts function I have isn't required for the handler. It's used to make the list of permissions readable in one string. You can always remove it or change it to your needs.

2
votes

That is not how error handling works. You should check out the error handling section of the docs for how to deal with missing permissions.

EDIT because people reminded me to give some more information

You shouldn't be handling errors through has_permissions. From what I can tell from your code, you are looking to send a message when the invoker doesn't have the permissions required to use the command. This can be achieved with an error handler. THe error handler will catch the MissingPermissions exception and do something based on that. Here is an example:

@commands.Cog.listener()
async def on_command_error(self, ctx, error): #this is the event that catches errors
    if isinstance(error, commands.MissingPermissions): #seeing if the error is the missing permissions error
        await ctx.send(f"You do not have the permission to do this, {ctx.author.mention}!")

This uses a global error handler, meaning that whenever an error is caught, this will catch it. However if you want an error handler for each command, all you have to change is the decorater and function name. EX:

@kick.error
async def on_kick_error(self, ctx, error):
    if isinstance(error, commands.MissingPermissions):
        await ctx.send(f"You do not have the permission to kick any member, {ctx.author.mention} ")

This one will only trigger on the kick command error, none of the others.