I am making this gist to show some examples of stuff you can do with discord.py, and also because the amount of up-to-date examples online is rather limited.
This will not be a guide for learning how to use the basics of the wrapper, but merely showing some code to get a better understanding of some of the things discord.py can do. I will therefore assume that anybody looking at this will understand the basics of both python and the wrapper in question.
I will also assume that asyncio
, discord.ext commands
and discord
are installed and imported, and that the commands.Bot
instance is stored in the variable bot
.
Do not just copy paste code from here, try to actually understand what is being done, and then implement it with your own code.
I also can not guarantee that the code in this gist is the most effective approach at what it is trying to do.
- Official discord.py discord server
- Official Python discord server
- Official discord developers server
- Official discord.py documentation
- Official discord API documentation
- Relatively decent beginners guide
- Preface
- Examples
- Bot commands
- Banning members
- Creating roles
- Adding roles
- Countdown/timer
- Sending messages
- User info
- Background task and command group
- Getting online members/offline members
- Editing every guild member
- Waiting for input
- Fetching audit log
- Sending DM's
- Editing all text channels and bot variable
- Purge command and specific error handling
- Getting data from website
- Pinging bot
- Bot events
- Other
- Bot commands
You can make a simple ban command like this
def ban_check(ctx, member):
# Check if the user"s "best" role is higher than the member he is trying to ban
return ctx.author.top_role > member.top_role
@bot.command()
# Check if user has the ability to ban members
@commands.has_permissions(ban_members=True)
async def ban(ctx, member: discord.Member, *, reason=None):
# If check comes back False
if not ban_check(ctx, member):
await ctx.send("Failed to ban, lacking role hierarchy.")
return
# Ban member and pass reason for audit log
await ctx.guild.ban(member, reason=reason)
await ctx.send(f"Banned {member} for reason *{reason}*")
Though sometimes you want to ban people that are not in the guild anymore, because discord.Member
works with cache we need to use a different way of banning.
guild.ban(discord.Object(id=USERIDHERE))
Using this, we can for example ban a person from every guild the bot is in
@bot.command()
# The command user should give a account ID
async def globalban(ctx, x: int):
# Fetching a user object using the given ID
user, l = await bot.fetch_user(x), []
msg = await ctx.send(f"Banning {user}")
# Loop over each guild in the bots available guilds
for g in bot.guilds:
# ban user with given object
await g.ban(discord.Object(id=x))
await asyncio.sleep(1)
l.append(g.name)
await msg.edit(content=f"Banned **{user}** from **{', '.join(l)}**")
Creating a role, only specifying it's name
@bot.command()
async def create_role(ctx, *, name):
# Create the role
await ctx.guild.create_role(name=name)
await ctx.send(f"Created role with name {name}")
Adding a role to everybody in the server
@bot.command()
async def addroles(ctx, *, role: discord.Role):
# Looping over all the guild members
for m in ctx.guild.members:
await m.add_roles(role)
# Sleeping to prevent API abuse
await asyncio.sleep(1)
await ctx.send("Added roles")
You can also specify specific users to add a role to:
@bot.command()
# We specify members as a tuple, so we can define multiple and iterate over it
async def addrole(ctx, role: discord.Role, *members: discord.Member):
for m in members:
await m.add_roles(role)
print(f":white_check_mark: Role {role} added to {m.mention}")
You can use while loops as well
@bot.command()
async def countdown(ctx, t: int):
await ctx.send(f"Counting down from {t}s")
while t > 0:
t -= 1
# Sleep for 1 second
await asyncio.sleep(1)
await ctx.send("Countdown end reached")
*, arg
is useful if you want to consume all as a string
@bot.command()
async def echo(ctx, *, message):
# Delete message that invokes command
await ctx.delete()
# Repeat back the input and auto delete after 20 seconds
await ctx.send(message, delete_after=20)
An announcement system for your bot could be made like this, where guild owners could simply opt in by making a text channel named bot-announcements
@bot.command()
async def botannouncement(ctx, *, message):
# Loop over each guild of all the guilds the bot is in
for guild in bot.guilds:
# Using discord.utils.get to get a text channel object by searching for a name
channel = discord.utils.get(guild.text_channels, name="bot-announcements")
await channel.send(message)
await asyncio.sleep(1)
You can loop over each text channel in a guild, and send a message
@bot.command()
async def sendToAll(ctx, *, message):
for channel in ctx.guild.text_channels:
await channel.send(message)
You can send a message to everybody in a guild with a specific role
@bot.command()
async def groupDM(ctx, role: discord.Role, *, message: str):
for member in role.members:
await member.send(message)
We can get a lot of user information
from datetime import datetime, timedelta, timezone
# Making a function that returns the members status
def getstatus(m):
if str(m.status) == "dnd":
return "do not disturb"
return m.status
@bot.command()
async def info(ctx, member: discord.Member):
# Calculating time since the user created his discord account using the member.created_at method
c_delta = datetime.utcnow() - member.created_at
c_ago = datetime.fromtimestamp(c_delta.seconds, tz=timezone.utc).strftime("%H:%M:%S")
c_at = member.created_at.strftime("%c")
# Getting join position by sorting the guild.members list with the member.joined_at method
join_pos = sorted(ctx.guild.members, key=lambda member: member.joined_at).index(member) + 1
# Defining discord.Embed instance
embed = discord.Embed(title=f"{member.name}#{member.discriminator}", timestamp=datetime.utcnow(), colour=0x000)
# Adding fields to the embed
embed.add_field(name="Status:", value=getstatus(member), inline=True)
embed.add_field(name="Guild name:", value=member.display_name, inline=True)
embed.add_field(name="Join position:", value=f"{join_pos}/{len(ctx.guild.members)}", inline=True)
embed.add_field(name="Created at:", value=f"{c_at}\n({c_delta.days} days, {c_ago} ago)", inline=True)
embed.add_field(name="ID:", value=member.id, inline=True)
embed.add_field(name="Bot:", value="✅ Yes" if member.bot else "❌ No", inline=True)
# Setting the thumbnail as the users profile picture
embed.set_thumbnail(url=member.avatar_url)
# Setting a footer
embed.set_footer(text=f"Requested by {ctx.author.name}", icon_url=ctx.author.avatar_url)
await ctx.send(embed=embed)
Output:
We can make command groups which will allow us make subcommands
from discord.ext import tasks
# Loop every 60 seconds
@tasks.loop(seconds=60)
async def loop_function(ctx, message):
await ctx.send(message)
# Specify that we don't want the 'first' command called when we use a subcommand
@bot.group(invoke_without_command=True)
async def loop(ctx):
await ctx.send("**1.** Start loop: `.loop start {message}`\n**2.** Stop loop: `.loop stop`")
# Subcommand named 'start', so called like this: "PREFIXloop start message goes here"
@loop.command()
async def start(ctx, *, message):
await ctx.send("**Starting loop...**")
# Starting the loop function
loop_function.start(ctx, message)
# Subcommand named stop
@loop.command()
async def stop(ctx):
await ctx.send("**Stopping loop...**")
# Stopping the loop
loop_function.stop()
@bot.command()
async def online(ctx):
online_m, offline_m = [], []
# Loop over each member in guild.members
for m in ctx.guild.members:
# Add to list of online members (online_m) if status is online/dnd, else add to offline_m
(online_m if str(m.status) in ("online", "dnd") else offline_m).append(str(m))
await ctx.send(f"Online: {', '.join(online_m)}\nOffline: {', '.join(offline_m)}")
I am changing the nickname as action here, but in theory it could be any action that edits a member
@bot.command()
async def nick(ctx, username: str):
for member in ctx.guild.members:
if member.id == ctx.guild.owner_id: # Bot's can't edit guild owners,
pass # so we pass that
else:
await member.edit(nick=username)
print(f"Changed {member}"s nickname to {username}..")
await asyncio.sleep(1)
# Defining a function that returns if the message content is in a list
def conversation_check(m):
return m.content.lower() in ["hello", "hi", "hey"]
@bot.command()
async def conversation(ctx):
await ctx.send("Hi!")
# Wait for a message that passes our check
await bot.wait_for("message", check=conversation_check)
await ctx.send("How are you?")
try:
# Storing the wait_for in a variable so we can get the return information from that,
# also wait for a maximum of 15 seconds
message = await bot.wait_for("message", timeout=15)
# If the message.content is bad or terrible we send a message
if message.content.lower() in ["bad", "terrible"]:
await ctx.send("oh.. :cry:")
# If the message.content is good, well or fine we send a message as well
elif message.content.lower() in ["good", "well", "fine"]:
await ctx.send("me too :smile:")
# Excepting the error if no reply is send in 15 seconds
except asyncio.TimeoutError:
await ctx.send("I hope well :smile:")
We can do the same thing with emoji's
@bot.command()
async def results(ctx):
# Store bot message and invoke message in variables:
msg, ini = await ctx.send("Would you like to see the results?"), ctx.message
# Put your to be reaction added emojis in an iterable:
reactions = ('✔️', '✖️')
# If we want to add multiple emoji's to a message, we have to loop
# over each emoji in the reactions tuple, and react with it to the msg:
for r in reactions:
await msg.add_reaction(r)
# Wait for a reaction_add event, store the reacted emoji and user
reaction, user = await bot.wait_for('reaction_add')
# If the reacted emoji is the checkmark:
if reaction.emoji == "✔️" and user == ctx.message.author:
# Clear the reactions from the original message
await msg.clear_reactions()
# Edit it to display some other content
await msg.edit(content="Here are the results")
# If the reacted emoji is the X:
elif reaction.emoji == "✖️" and user == ctx.message.author:
# We delete the messages
await ini.delete()
await msg.delete()
@bot.command()
async def logs(ctx):
actions = []
# Loop over audit logs and fetch all the banning actions, with a limit of 10
async for entry in ctx.guild.audit_logs(limit = 10):
time = entry.created_at.strftime("%d-%m-%Y %H:%M:%S")
# Add to the list
actions.append(f"`{entry.user}` did `{entry.action}` at `{time}` to `{entry.target}`\n\n")
# Send list as embed
embed = discord.Embed(title="Audit log", description=''.join(actions), colour=0x000)
await ctx.send(embed=embed)
Output:
This will work well with a modmail command for example
@bot.command()
# We are defining member as a discord.Member object, it will convert automatically for us
async def dm(ctx, member: discord.Member, *, message: str):
try:
# Send the message by calling the .send method on the member object
await member.send(f"Staff 🛠️: {message}")
await ctx.send(f"Successfully sent message to {member}.")
# If user is not in guild or has blocked the bot
except discord.Forbidden:
await ctx.send(f"Failed to send message to {member}.")
The action can be anything, but in the code below I am changing the slowmode values for each text channel You could see it as an anti 'raid' command
# Creating a command group
@bot.group(invoke_without_command=True)
async def slowmode(ctx):
await ctx.send("**Turn on with** `slowmode overwrite {time(s)}`\n**Reset back to original with** `slowmode reset`")
# Subcommand
@slowmode.command()
async def overwrite(ctx, delay: int):
# Creating a bot variable so we can call it accross commands
bot.sm_list = []
msg = await ctx.send("**Overwriting..**")
# Looping over each text channel in the context guild
for tc in ctx.guild.text_channels:
# Adding the original slowmode to the bot variable sm_list
bot.sm_list.append(tc.slowmode_delay)
# Editing the text channel with the new slowmode
await tc.edit(slowmode_delay=delay)
print(f"Overwrited {tc.name}'s slowmode to {delay}")
# Editing message
await msg.edit(content=f"**Changed slowmode for all channels to** `{delay}`")
# Subcommand
@slowmode.command()
async def reset(ctx):
msg = await ctx.send("**Changing back...**")
# Looping over each text channel in the context guild together with the bot variable
# containing all the former slowmode values
for tc, i in zip(ctx.guild.text_channels, bot.sm_list):
await tc.edit(slowmode_delay=i)
print(f"Changed {tc.name}'s slowmode back to {i}")
await msg.edit(content="**Reset slowmode for all channels to original**")
@bot.command()
# Only allow people with the manage_messages permission to use the command
@commands.has_permissions(manage_messages=True)
async def purge(ctx, limit: int):
# Mass deleting the messages with the specified limit
await ctx.channel.purge(limit=limit)
# Handle error where someone does not have manage_messages permission
@purge.error
async def purge_error(ctx, error):
if isinstance(error, commands.MissingPermissions):
await ctx.send("You require the manage messages permission to use this command")
Since everything we do is asynchronous (using coroutines), we have to use an asynchronous requests library as well, discord.py is using aiohttp to send it's requests to the discord API gateway, so thefore it will always be the logical choice
import aiohttp
@bot.command()
async def dog(ctx):
# Create an aiohttp session
async with aiohttp.ClientSession() as session:
# Send a request to a website
async with session.get('https://dog.ceo/api/breeds/image/random') as resp:
# If response came back OK
if resp.status == 200:
# Convert to json
js = await resp.json()
# Sending content of that json
await ctx.send(js['message'])
else:
print(resp.status)
session.close()
import time
@bot.command()
async def ping(ctx):
start = time.perf_counter()
message = await ctx.send("Resolving...")
end = time.perf_counter()
# Calculate response time
duration = (end - start) * 1000
# bot.latency is the latency to the API, the total latency here is the amount of time in ms it took for the bot to respond to the command invoke
await message.edit(content=f"**Web socket latency:** {round(bot.latency * 1000)}ms\n**Total latency:** {duration:.0f}ms")
bot.guilds
returns a list of all the guilds the bot is in, so calling len on that will logically give you the amount of guilds.
@bot.event
async def on_ready():
print(f"Bot {bot.user.name} is ready!")
# Function automatically called every 5 minutes
@tasks.loop(minutes=5)
async def loop_function():
# Set the activity as "Watching" + the amount of guilds, and users the bot is watching over.
activity = discord.Activity(name=f"over {len(bot.guilds)} guilds\nand {len(bot.users)} users!", type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
# Start the loop
loop_function.start()
This is global error handling, if you for instance want to except every CommandOnCooldown exception, you can do it like this
@bot.event # Error handling
# on_command_error takes context and error
async def on_command_error(ctx, error):
# Here we are handling a command on cooldown error globally
if isinstance(error, commands.CommandOnCooldown):
# error here has some methods, like retry_after which gives you the time in seconds left on cooldown
await ctx.send(f"{ctx.author.mention}, command is on cooldown. Try again in {error.retry_after:.2f}s", delete_after=15)
# Make sure to add an else clause, so that other errors are displayed in console.
else:
print("[!] Error has occured, information below.")
print("-----------------------------------------------------------------")
print("[*] Error:", error)
print("[*] Error context:", ctx)
print("-----------------------------------------------------------------")
When a user joins the discord guild
@bot.event
# on_member_join takes a member argument
async def on_member_join(member):
# Get the channel where it said the person joined, which is always the system_channel
channel = member.guild.system_channel
# Send a message there
await channel.send(f"Welcome to {member.guild.name}, {member.mention}!")
# Adding a role
role = member.guild.get_role(ROLEIDGOESHERE)
# the reason kwarg is for the audit log
await member.add_roles(role, reason="Automatic role by bot")
When a user leaves the discord guild
@bot.event
async def on_member_remove(member):
channel = member.guild.system_channel
await channel.send(f"{member.mention} left the guild...")
Every message send in a guild could be processed by a on_message event.
@bot.event
async def on_message(message):
# If the message sender is the bot itself, stop to prevent endless loop.
if message.author.id == bot.user.id:
return
# Reacting to bot being mentioned
if bot.user in message.mentions:
await message.channel.send("Stop mentioning me :rage:")
# Getting current prefix by calling a function
if message.content.startswith("prefix"):
await message.channel.send(f"Current prefix: {get_pre(bot, message)}")
# Autodeleting certain words
if message.content.lower() in ["words", "you", "want", "blocked"]:
await message.delete()
await message.author.send(f"Do not say {message.content}")
# Make sure to process commands if no on_message event was triggered, else commands won't work.
await bot.process_commands(message)
Here we use are using on_message to create a simple modmail concept where every message send to the bot, will be send into a logging channel. This will work well with a DM command for example
@bot.event
# on_message takes a message argument
async def on_message(message):
# Prevent endless loop
if message.author.id == bot.user.id:
return
# If the message is not in a guild
if not message.guild:
# Define the log channel with guild ID and channel ID
logchannel = bot.get_guild(GUILDIDGOESHERE).get_channel(CHANNELIDGOESHERE)
# Send a message as embed in that log channel
embed = discord.Embed(title="Modmail", colour=0x000, description=f"**From:** {message.author.mention}\n\n*{message.content}*", timestamp=datetime.utcnow())
embed.set_footer(text=message.author, icon_url=message.author.avatar_url)
await logchannel.send(embed=embed)
# Process commands
await bot.process_commands(message)
Output:
@bot.event
async def on_message_delete(message):
# Mention the user who's message got deleted, and send the content
await message.channel.send(f"I see you :eyes: {message.author.mention}: {message.content}")
Load all the files with a .py extension in a folder named /cogs
import os
for filename in os.listdir("./cogs"):
if filename.endswith(".py"):
bot.load_extension(f"cogs.{filename[:-3]}")
print(f"Loaded {filename}")
.env files are useful if you want to store tokens/IDs discretely separated from your code
pip install python-dotenv
In .env file:
DISCORD_TOKEN = NzMwNzM5OTUyMzkxOTQ2MjUy.EXAMPLEDISCORDTOKENvljgg7Iw
In .py file:
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
...
bot.run(TOKEN)
Global checks are useful if you want to check something before any command is used, for example not allowing people to use commands from a DM. It has to return True for every command that is used.
@bot.check
async def block_dms(ctx):
return ctx.guild is not None
There are also command specific checks, like global checks they can only take context
def channel_check(ctx):
return ctx.channel.id == BOTCHANNELIDHERE
@bot.command()
# Command will only work when the ctx.channel.id is that of the specified text channel ID, else commands.CheckFailure is raised
@commands.check(channel_check)
async def foo(ctx):
pass
You can make a function that returns a prefix, this is called every time a command is used
def get_pre(bot, message):
# If rep is in the message content, return '+' as the prefix
if "rep" in message.content:
return "+"
# If the message comes from this guild ID, return '!' as the prefix
elif message.guild.id == SOMEGUILDID:
return "!"
# Else return '?' as the prefix
return "?"
bot = commands.Bot(command_prefix=get_pre)
You might want to store a custom prefix for each guild. Though this might be useful for guild owners, you can imagine that searching up an ID in a database and returning a prefix for every command used is not very efficient. Instead I would recommend to just allow the usage of multiple prefixes, or think of one you are sure is not going to be used by someone else.
# Bot will accept when mentioned as a prefix or other 2 prefixes
bot = commands.Bot(command_prefix=commands.when_mentioned_or("P:", "B!"))
These are all ways to specify the embed colour as red
# Converting from HSV
discord.Embed(colour=discord.Colour.from_hsv(0,1,1))
# Converting from RGB
discord.Embed(colour=discord.Colour.from_rgb(255,0,0))
# Using basic discord colours
discord.Embed(colour=discord.Colour.red())
# Specifying int as base 16 using int() function
discord.Embed(colour=int('ff0000', 16))
# Specifying int as base 16, using 0x
discord.Embed(colour=0xff0000)
# As base 10 integer
discord.Embed(colour=16711680)
You can add cooldowns to commands easily, more info about available buckettypes here
@bot.command()
# 1 use per 180 seconds per guild, commands.CommandOnCooldown exception raised if someone tries to use it while it is on cooldown
@commands.cooldown(1, 180, commands.BucketType.guild)
async def foo(ctx):
pass
@bot.command()
async def files(ctx):
# Using discord.File to upload an image and send it
await ctx.send(file=discord.File('my_png.png'))
# Use files kwarg for multiple files:
my_files = [
# Zip file
discord.File('my_zip.zip'),
# MP3 in a different folder
discord.File('assets//my_mp3.mp3')
]
# Upload files
await ctx.send(files=my_files)