Permission Check Discord.py Bot - python

I am working on a discord bot for basic moderation which does kick, ban and mute for now at least. But the problem is other members can use it too. I want only a few specified role who can use it.
Don't want to work on it depending on the #role either because the name of the roles across different servers aren't the same. Also wanting to keep the bot as simple as possible.
Now, I started out as this:
#client.command(name='ban')
async def mod_ban(member: discord.User):
try:
await client.ban(member, delete_message_days=0)
await client.say('**{0}** has been banned.'.format(str(member)))
except Exception as error:
await client.say(error)
But any member can use the commands then. So, tried follow this one = Permission System for Discord.py Bot and ended up with this:
#client.command(name='ban')
async def mod_ban(context, member: discord.User):
if context.message.author.server_premission.administrator:
try:
await client.ban(member, delete_message_days=0)
await client.say('**{0}** has been banned.'.format(str(member)))
except Exception as error:
await client.say(error)
else:
await client.say('Looks like you don\'t have the perm.')
Which lands me with this error: ;-;
raise MissingRequiredArgument('{0.name} is a required argument that is missing.'.format(param))
discord.ext.commands.errors.MissingRequiredArgument: member is a required argument that is missing.
Also, besides context.message.author.server_premission.administrator I don't only want the roles with Admin perm to use this command. I also want a few other roles with have few perms like manage message, manage roles etc. to use to command too.
Thanks in advance for the help! Also, sorry if I've missed anything stupid or silly ;-;

You aren't passing the context into the coroutine in your second example (and as #Andrei suggests, you can only ban members):
#client.command(name='ban', pass_context=True)
async def mod_ban(context, member: discord.Member):
...
Also, I should probably update my answer to that question. In the context of commands, you can use the very powerful checks built into discord.ext.commands to do a lot of this for you. has_permissions does exactly what you're looking for, validating that the user has any of the necessary permissions.
from discord.ext.commands import has_permissions, CheckFailure
#client.command(name='ban', pass_context=true)
#has_permissions(administrator=True, manage_messages=True, manage_roles=True)
async def mod_ban(ctx, member: discord.Member):
await client.ban(member, delete_message_days=0)
await client.say('**{0}** has been banned.'.format(str(member)))
#mod_ban.error
async def mod_ban_error(error, ctx):
if isinstance(error, CheckFailure):
await client.send_message(ctx.message.channel, "Looks like you don't have the perm.")

As far as I can see in the discord.py documentation discord.User is not the same as discord.Member.
Try to change
async def mod_ban(context, member: discord.User):
to
async def mod_ban(context, member: discord.Member):

If you are using discord.py rewrite, you can use checks (Discord.py rewrite checks)
Which (obviusly) checks for certain things suchs as roles or permissions on the command invoker
You can use both of this decorators, below your first decorator
#commands.has_role("rolename"/roleid)
#commands.has_any_role("rolename"/roleid,"rolename"/roleid,"rolename"/roleid ...)
Where rolename is a string containing the EXACT name of the role, letter by letter and space by space, and roleid is the id of the role, which, in case it's mentionable, you can get it by typing #rolename on any of your server chatrooms
Note how you must use the second decorator if you want to pass more than one role to check

Related

How do I make my discord.py bot get a user's avatar?

I'm trying to make my discord.py bot be able to send a user's avatar into the chat when it is asked to. This is my current code:
if any(word in msg for word in avatar_words):
async def avatar(ctx,*,avamember:discord.Member=None):
userAvatarUrl = avamember.avatar_url
await ctx.send(userAvatarUrl)
This code doesn't work and I'm not exactly sure why. I would also like to give the bot the ability to have the bot get a mentioned user's avatar as opposed to just the author's, but yet again I'm unsure exactly how.
Yes, I have looked around beforehand and all of the solutions I found did not work. The one shown is the only one that did not produce error messages.
async def avatar(ctx, *, member: discord.Member = None):
if not member:
member = ctx.message.author
em = discord.Embed(title=str(member), color=0xAE0808)
em.set_image(url=member.avatar_url)
await ctx.send(embed=em)

Discord.py set user id as an argument

I want to create a command where its using an user id as an argument after ctx to make the bot send the user mention. The problem is, i dont know whats the syntax for user id. Im already looking through the documentation but didnt find the answer there.
Heres the code.
#client.command()
async def userid(ctx, member: discord.Member.id):
await ctx.send(f'{member.mention})
You are nearly there.
The typehint converts usernames, mentions, ID and display_name into a Member object, this object contains all of the above.
#client.command()
async def userid(ctx, member: discord.Member):
await ctx.send(member.mention)

A presence/activity set command?

So, I was wondering if there could be a command I could write that allows me to set the bots presence and activity (ex. ~~set presence idle or ~~set activity watching "people typing ~~help") or something like that.
Unrelated question: How do I set commands to be used by me only?
I haven't found any example code for this, and i'm a beginner.
You can use the is_owner check to ensure that you are the only person who can invoke a command.
To change the presence or status of the bot, use the change_presence method:
from discord.ext.commands import Bot, is_owner
from discord import Status, Activity, ActivityType
bot = Bot("~~")
def getEnum(enum):
def getter(arg):
return enum[arg]
return getter
#bot.group(invoke_without_command=True)
#is_owner()
async def set(ctx):
await ctx.send("You must provide a subcommand.")
#set.command()
async def presence(ctx, status: getEnum(Status)):
await bot.change_presence(status=status)
#set.command(invoke_without_command=True)
async def activity(ctx, type: getEnum(ActivityType), *, description):
await bot.change_presence(activity=Activity(type=type, name=description))
#set.error
async def set_error(ctx, error):
if isinstance(error, BadArgument):
await ctx.send(error.message)
await ctx.send(error.args)
bot.run("token")
The above will fail silently if you try to provide an unrecognized name to Status or ActivityType, you could also try writing an error handler to provide some feedback.

Permission System for Discord.py Bot

I am in the process of making a discord bot using discord.py and asyncio. The bot has commands like kick and ban which obviously should not be available to normal users.
I want to make a simple system which will detect what permissions the user's role has using ctx.message.author to get the user who sent the command.
I do not want the bot to detect a specific role name as these vary across servers. I also prefer not to have multiple files for the bot to keep it simple.
I have seen the discord.py documentation and various other sources but none contain examples of how to implement the various methods they talk about.
As an example, here is a single command from my bot:
async def kick(ctx, userName: discord.User):
if True: #ctx.message.author.Permissions.administrator
await BSL.kick(userName)
else:
permission_error = str('Sorry ' + ctx.message.author + ' you do not have permissions to do that!')
await BSL.send_message(ctx.message.channel, permission_error)
Where the if else statement is my attempt of doing this on my own. The #ctx.message.author.Permissions.administrator is commented out as it does not work and replaced with True for testing purposes.
Thank you for any help and suggestions in advance.
Permissions is the name of the class. To get the message authors permissions, you should access the guild_permissions property of the author.
if ctx.message.author.guild_permissions.administrator:
# you could also use guild_permissions.kick_members
Update:
A better way to validate the permissions of the person invoking the commands is by using the check feature of the commands extension, specifically the has_permissions check. For example, if you wanted to open your command only to people who had either the manage_roles permission or the ban_members permission, you could write your command like this:
from discord import Member
from discord.ext.commands import has_permissions, MissingPermissions
#bot.command(name="kick", pass_context=True)
#has_permissions(manage_roles=True, ban_members=True)
async def _kick(ctx, member: Member):
await bot.kick(member)
#_kick.error
async def kick_error(ctx, error):
if isinstance(error, MissingPermissions):
text = "Sorry {}, you do not have permissions to do that!".format(ctx.message.author)
await bot.send_message(ctx.message.channel, text)
You could also use decorators.
#bot.command(name = "Kick")
#bot.has_permissions(kick_user = True)
#bot.bot_has_permissions(kick_user = True)
async def _kick(ctx, member: Member):
#Do stuff...
The advantage of checking user and bot permissions means it is easier to handle errors from either providing useful "Insufficient Permission" error messages.
The tips found the accepted answer may not work:
There may be compatibility issues with the rewrite version of the discord.py library and the pre-rewrite versions, which remain non-obsolete, non-deprecated, and still in use.
The bot should also check it's own permissions, to rule out one reason for the error.
If there is an error, or permissions for the bot itself are invalid, the bot should say something, correct?
Something needs to be implemented to prevent the bot from attempting to work this command in a DM or group context. It will almost always error.
I propose the following solution for pre-rewrite (assuming you use the command extension):
import discord
from discord.ext import commands
import time
#bot.command(pass_context=True,description="Kicks the given member. Please ensure both the bot and the command invoker have the permission 'Kick Members' before running this command.")
async def kick(ctx, target:discord.Member):
"""(GUILD ONLY) Boot someone outta the server. See 's!kick' for more."""
if not str(ctx.message.channel).startswith("Direct Message with "):
msg=await bot.say("Checking perms...")
time.sleep(0.5)
if ctx.message.server.me.server_permissions.kick_members:
if ctx.message.author.server_permissions.kick_members:
await bot.edit_message(msg,new_content="All permissions valid, checking issues with target...")
time.sleep(0.5)
if target==ctx.message.server.owner:
await bot.edit_message(msg, new_content="All permissions are correct, but you're attempting to kick the server owner, whom you can't kick no matter how hard you try. Whoops!")
else:
if target==ctx.message.server.me:
await bot.edit_message(msg, new_content="Whoops! All permissions are corrent, but you just tried to make me kick myself, which is not possible. Perhaps you meant someone else, not poor me?")
else:
await bot.edit_message(msg, new_content="All permissions correct, and no issues with target being self or server owner, attempting to kick.")
time.sleep(2)
try:
await bot.kick(target)
await bot.edit_message(msg, ":boom: BAM! ***kicc'd***")
except Exception:
await bot.edit_message(msg, new_content="I was unable to kick the passed member. The member may have a higher role than me, I may have crashed into a rate-limit, or an unknown error may have occured. In that case, try again.")
else:
await bot.edit_message(msg, new_content="I've the correct permissions, {}, but you do not. Perhaps ask for them?").format(ctx.message.author.mention)
else:
await bot.edit_message(msg, new_content="I'm just a poor bot with no permissions. Could you kindly grant me the permission `Kick Members`? Thanks! :slight_smile:")
else:
await bot.say("'Tis a DM! This command is for servers only... try this again in a server maybe? :slight_smile:")
Based on the current version of discord.py, using discord.ext.commands, you can do this:
Replace all <> with what it says.
import discord
from discord.ext import commands
bot = commands.Bot(command_prefix = "<this can be whatever you want>", intents = discord.intents.all())
#make sure to enable all intents in the discord dev portal. I use all intents to make things simple.
#bot.command()
#commands.has_permissions(<permission> = True)
async def ban():
#the ban commands go here
The permission has to be exactly like how it says in the api documentation here.
These permission ID's are for discord, but they are the same for discord.ext.
The ID for banning is ban_members. In the decorator #commands.has_permissions, remember to NOT put the ID in quotes. Also remember to add = True to the end of the id.

Discord.py trouble with move_member()

Im having trouble with using move_member() for a python bot. The purpose of the command is to "kick" a user by moving them to a channel, then deleting it so that they disconnect from the voice call, but do not need an invite back to the server. I am aware that just moving a user accomplishes this purpose, but I would like for a user to disconnect instead.
import discord
import random
import time
import asyncio
from discord.ext import commands
from discord.ext.commands import Bot
bot = commands.Bot(command_prefix="!")
#bot.event
async def on_ready():
await bot.change_presence(game=discord.Game(name='with fire'))
print("Logged in as " + bot.user.name)
print(discord.Server.name)
#bot.command(pass_context=True)
async def kick(ctx,victim):
await bot.create_channel(message.server, "kick", type=discord.ChannelType.voice)
await bot.move_member(victim,"kick")
await bot.delete_channel("bad boi")
bot.run('TOKEN_ID')
the line gives the error:
The channel provided must be a voice channel
await bot.move_member(victim,"kick")
and this line gives this error:
'str' object has no attribute 'id'
await bot.delete_channel("kick")
Im pretty sure you have to get the channel id instead of "kick", but I don't see exactly how to do so, because the code below isnt working, even when I replace
ChannelType.voice to discord.ChannelType.voice
discord.utils.get(server.channels, name='kick', type=ChannelType.voice)
delete_channel('kick') will not work because you need to pass in a channel object and not a string.
You do not need to use discord.utils to get the channel you want. The create_channel returns a channel object, so you should be able to use that.
But, you do need to get the Member object that you're going to kick. You also made the mistake of referencing message.server rather than ctx.message.server
#bot.command(pass_context=True)
async def kick(ctx, victim):
victim_member = discord.utils.get(ctx.message.server.members, name=victim)
kick_channel = await bot.create_channel(ctx.message.server, "kick", type=discord.ChannelType.voice)
await bot.move_member(victim_member, kick_channel)
await bot.delete_channel(kick_channel)
Now if you're using rewrite library, you would have to do the following
#bot.command()
async def kick(ctx, victim):
victim_member = discord.utils.get(ctx.guild.members, name=victim)
kick_channel = await ctx.guild.create_voice_channel("kick")
await victim_member.move_to(kick_channel, reason="bad boi lul")
await kick_channel.delete()
As abccd mentioned in the comments, this is evaluated as a string, which will not guarantee the fact that you'll be kicking the right person. discord.utils.get will grab the first result, and not necessarily the correct user if multiple users have the same name.
A better approach would be to use #user or to use UserIDs. Here's an example in the old library
#bot.command(pass_context=True)
async def kick(ctx):
victim = ctx.message.mentions[0]
kick_channel = await bot.create_channel(ctx.message.server, "kick", type=discord.ChannelType.voice)
await bot.move_member(victim,kick_channel)
await bot.delete_channel(kick_channel)
I would highly recommend to start using the rewrite library since it's much more Pythonic and it's going to be the new library in the future anyways.
According to the Discord documentation, the call to delete_channel expects a parameter of type Channel.
For more information on the Channel class, refer to the Channel documentation
If I understand what you're attempting to do, for your code to run as expected, you would need to maintain a reference to the channel you are using as your temporary channel, and then change your offending line to:
await bot.delete_channel(tmp_channel)

Categories