Skip to content

Instantly share code, notes, and snippets.

@Anubhav1603
Last active October 22, 2023 03:02
Show Gist options
  • Save Anubhav1603/e0be4e51c3e2ff50ffe9c2b46e1f667f to your computer and use it in GitHub Desktop.
Save Anubhav1603/e0be4e51c3e2ff50ffe9c2b46e1f667f to your computer and use it in GitHub Desktop.
Code examples of discord.py rewrite

discord.py code examples

Preface

Introduction

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.

Sources

Index

Examples

Bot commands

Banning members

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 roles

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 roles

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}")

Countdown/timer

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")

Sending messages

*, 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)

User info

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:

user info output

Background task and command group

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()

Getting online members/offline members

@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)}")

Editing every guild member

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)

Waiting for input

# 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()

Fetching audit log

@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:

audit logs output

Sending DM's

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}.")

Editing all text channels and bot variable

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**")

Purge command and specific error handling

@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")

Getting data from website

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()

Pinging bot

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 events

On ready and setting activity

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()

Error handling

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("-----------------------------------------------------------------")

User joining/leaving guild

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...")

On message

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)

Modmail using on 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:

modmail output

Message deleted

@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}")

Other

Loading cogs

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}")

Using .env

.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 / Command checks

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

Different prefixes

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!"))

Embed colours

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)

Command cooldowns

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

Uploading files

@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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment