Create a Python Discord Bot in Minutes: Easy Tutorial

A Discord bot is an automated program that runs on the Discord platform. Bots can do various things, from playing music, sending automated messages, and managing roles, to kicking users who break rules, and many other tasks.

The core functionality of a discord bot is built on the basis of events. For example, when a new message event occurs, the bot can send a message back, essentially allowing it to respond.

In this tutorial, we’ll create a simple Python discord bot. We’ll use the discord.py library, which simplifies the process of creating bots in Python. Let’s dive into the world of Discord bots!

 

 

Understanding Discord’s structure

Before we start building our bot, we need to understand the structure of Discord:

  • Discord Server: This is a space for a community of users. It can have multiple channels.
  • Channel: A space inside a server where users can talk. Channels can be text or voice-based.
  • Message: A piece of communication sent by a user. This can be in the form of text, image, video, etc.
  • User: A member of a server. Users can have different roles.
  • Role: A set of permissions assigned to a group of users.
  • Bot User: An automated user that can interact with real users by responding to commands.

 

Bot creation requirements

Creating a Discord bot is a simple, straightforward procedure that can be done through the developer portal or any code editor.
Before adding the code, a bot account needs to be created.
Go to the Discord developer portal at https://discord.com/developers/applications, log in, and click on ‘New Application’.
Name this application and click on the option bot from the list on the left side.
Now, add a bot and copy the token for further use after naming it.

demo of how to grab a bot token

A Discord bot may need to access data that Discord qualifies as ‘sensitive’ in nature.
To give your bot access to privileged intents, you need to enable ‘Privileged Gateway Intents’ in the Developer Portal under the bots section.
This is only possible for unverified bots that are a part of fewer than 100 servers.
For verified bots, privileged intents need to be requested.

Enable Intents

 

Discord API

The Discord API allows for simpler and deeper integration with other services that enable users to have a smoother user experience.
The API is an open API that can be used to serve requests for bots and OAuth2 integration.
Various versions of the API are in use, you can use the preferred version by adding it to the request path such as https://discord.com/api/v{version_number}.
If the version is not mentioned then the default version is used to follow operations.

 

Create your first Python Discord bot

To start making a Discord bot in Python, the library we will use ‘discord.py’ which implements Discord’s API extensively. You will need the pip command to install discord.py.

pip install -U discord.py

We can now begin making the connection with the API.

import discord
TOKEN = "Insert Your Token here"
discord_client = discord.Client()
@discord_client.event
async def on_ready():
    print(f'{discord_client.user} Welcome to  Discord!')
discord_client.run(TOKEN)

This code is used to set up the initial connection with Discord. We first import the required library discord.
The token value can be obtained from the developer portal. You can place the token within the program in the placeholder string provided above.

For a more secure approach, you can place it in a .env file and refer to the same in your program.
Hard-coding secure keys in the program is not a very good developer practice. We are doing it here for demonstration purposes.
The discord_client object is an instance of the Client class, which is the connection to Discord.
@client.event is used to register an event, it is called when something happens i.e. some event is triggered.
The on_ready function will be called when the connection is set up and the bot is ready to take further steps.
The final step then runs the bot using the login token specified above.
Now run the code to see an established connection.

Output:

demonstration of a simple bot in Python

Welcome new members to the server

To print a welcome message when new users join the server, let’s add a new function on_member_join to the Python file.

import discord
from discord.ext import commands
TOKEN ="Insert Your token here"
intents=discord.Intents.all()
client = commands.Bot(command_prefix=',', intents=intents)
@client.event
async def on_member_join(member):
    await member.create_dm()
    await member.dm_channel.send(f'Hi {member.name}, welcome to the test Discord server!')
client.run(TOKEN)

Output:

demonstration of a welcome of new user by discord bot

The welcome_member function uses create.dm method to send the welcome message as a direct message to the user on joining.

Intents are permissions for the bot that are enabled based on the features necessary to run the bot. To run on_member_join we need to ensure intents are enabled.

 

Discord Bot Commands

Commands are short directions to perform a particular action or specific task.
Many Discord bots that are available in the bot store have multiple pre-configured commands that can be enabled or disabled by visiting the dashboard.
Besides the command, the prefix required to run the command in Discord will also be available. These prefixes can be of various types such as exclamation marks and many more.

Discord has various types of commands such as chat commands, text commands, and slash commands. The slash-type commands are always executed by using a slash as the prefix.

The text commands need to be entered in the text box based on the syntax of the command, whereas chat commands are used by directly entering the command in the text box.

import discord
import random
from discord.ext import commands
TOKEN ="Insert Your token here"
discord_bot = commands.Bot(command_prefix='!')
@discord_bot.command(name='morningquote')
async def msg(ctx):
    quotes = [
        "It's a new day",
        (
            "Be positive"
        ),
    ]
    response = random.choice(quotes)
    await ctx.send(response)
discord_bot.run(TOKEN)

While using commands, we use discord_bot.command to set the prefix for the command as ! and pass the name for the command as “morningquote”.
The command will be executed by the bot when a user types !morningquote in the chat.

Output:

example of a discord bot command

 

Adding command aliases

Sometimes, you might want to have multiple names for a single command. For example, you might want a !hello command to also respond to !hi.

This can be done with command aliases. Here’s how to add an alias to a command:

@bot.command(aliases=['hi'])
async def hello(ctx):
    await ctx.send('Hello!')

With this code, both !hello and !hi will trigger the command and make the bot respond with ‘Hello!’. You can add as many aliases as you like:

@bot.command(aliases=['hi', 'hola', 'bonjour', 'hallo'])
async def hello(ctx):
    await ctx.send('Hello!')

Now !hello, !hi, !hola, !bonjour, and !hallo will all trigger the same command.

 

Implementing help commands

Creating a help command for your bot can provide users with information on how to use the bot’s commands.

Luckily, discord.py provides a built-in help command that can be easily customized. Here’s a basic example:

bot = commands.Bot(command_prefix='!', help_command=commands.DefaultHelpCommand())

With this line of code, if you type !help in a channel that the bot is in, it will respond with a list of commands that the bot can handle, along with a brief description of each command.

You can customize the help command by subclassing commands.DefaultHelpCommand and overriding its methods. However, for most use cases, the default help command will be sufficient.

 

Commands with Options

Commands with options can add more functionality to your discord bot. These types of commands are useful when you want to let your users choose between multiple options.

Let’s create a basic voting system where a user can create a vote with multiple options, and other users can vote by reacting to the bot’s message.

from collections import defaultdict
votes = defaultdict(int)

@bot.command()
async def vote(ctx, *options):
    global votes
    votes = defaultdict(int)  # Reset previous votes

    embed = discord.Embed(title="Vote", description="\n".join(f"{i}: {option}" for i, option in enumerate(options, start=1)))
    message = await ctx.send(embed=embed)

    # Add reactions
    for i in range(1, len(options) + 1):
        await message.add_reaction(f'{i}\N{COMBINING ENCLOSING KEYCAP}')

    def check(reaction, user):
        return user != bot.user and reaction.message.id == message.id and reaction.emoji.endswith('\N{COMBINING ENCLOSING KEYCAP}')

    while True:
        reaction, user = await bot.wait_for('reaction_add', check=check)
        option = int(reaction.emoji[0]) - 1
        votes[option] += 1
        await reaction.remove(user)  # Optional: remove the user's reaction

@bot.command()
async def results(ctx):
    global votes

    max_votes = max(votes.values())
    winners = [option for option, vote in votes.items() if vote == max_votes]
    await ctx.send(f"The winner(s) is/are option(s) {', '.join(map(str, winners))}!")

In this example, vote is a command that accepts any number of arguments, which are the options to vote for. The bot sends an embed listing the options, and adds reactions corresponding to each option.

When a user reacts with a number, the votes dict is updated with the corresponding option.

The results command then determines the option with the most votes, and sends a message with the result.

 

Commands with Multiple Arguments

Commands with multiple arguments enable your Discord bot to accept multiple inputs at once.

These commands can be useful for a variety of tasks, such as calculating a sum, parsing complex user inputs, or handling multiple selections.

Let’s create a command that accepts two arguments and adds them together:

@bot.command()
async def add(ctx, num1: int, num2: int):
    result = num1 + num2
    await ctx.send(f'The sum is {result}.')

In this example, add is a command that accepts two integers as arguments. The bot calculates the sum of the two numbers and sends a message with the result.

The num1: int and num2: int syntax in the command definition tells discord.py to automatically convert the arguments to integers.

If a user enters something that can’t be converted to an integer, discord.py will automatically send an error message.

For more complex commands with multiple arguments, you can use *args or **kwargs to accept any number of arguments. Here’s an example of a command that adds together any number of numbers:

@bot.command()
async def add(ctx, *nums: int):
    result = sum(nums)
    await ctx.send(f'The sum is {result}.')

In this example, nums is a tuple containing all the arguments passed to the command. The sum() function adds them all together.

This command can handle any number of arguments – for example, “!add 1 2 3” would work just as well as “!add 1 2”.

 

Dealing with Invalid Commands

Discord.py provides a simple mechanism for handling these errors: the @command.error decorator. This decorator allows you to specify a function to be called when an error occurs while running a command.

Here’s an example of how you can use this decorator to handle errors:

@bot.command()
async def add(ctx, num1: int, num2: int):
    result = num1 + num2
    await ctx.send(f'The sum is {result}.')

@add.error
async def add_error(ctx, error):
    if isinstance(error, commands.BadArgument):
        await ctx.send('Please make sure to provide two numbers.')
    elif isinstance(error, commands.MissingRequiredArgument):
        await ctx.send('You forgot to provide one or both numbers.')
    else:
        await ctx.send('An unknown error occurred.')

In this code snippet, the add_error function is defined as an error handler for the add command.

If the error is a BadArgument error, which happens when the arguments can’t be converted to the required types, the bot sends a message asking for two numbers.

If the error is a MissingRequiredArgument error, which happens when the user forgets to provide one or both numbers, the bot sends a message notifying the user.

If the error is something else, the bot simply notifies the user that an error occurred.

 

Applying Cooldowns to Commands

In a busy server, it’s possible that some users might spam your bot’s commands, which could lead to rate limits or performance issues.

To prevent this, you can apply a cooldown to your bot’s commands. A cooldown means that each user must wait a certain amount of time between uses of a command.

Discord.py provides a decorator, @commands.cooldown(), which allows you to easily set a cooldown for your commands. This decorator takes three arguments:

  1. The number of times the command can be used before hitting the cooldown.
  2. The period (in seconds) within which the command can be used that number of times.
  3. The type of cooldown (per user, per channel, per server, or globally).

Here’s an example of how to use the @commands.cooldown() decorator to apply a cooldown to a command:

@bot.command()
@commands.cooldown(1, 5, commands.BucketType.user)
async def echo(ctx, *, message: str):
    await ctx.send(message)

In this example, the echo command has a cooldown of 5 seconds per user. This means that each user can only use the command once every 5 seconds.

If a user tries to use the command while it’s on cooldown, discord.py will automatically send an error message.

You can also handle the CommandOnCooldown error to send a custom message:

@echo.error
async def echo_error(ctx, error):
    if isinstance(error, commands.CommandOnCooldown):
        await ctx.send(f'This command is on cooldown, please try again in {error.retry_after:.2f} seconds.')
    else:
        await ctx.send('An unknown error occurred.')

In this code snippet, when a CommandOnCooldown error is raised, the bot sends a message notifying the user of how long they need to wait before they can use the command again.

 

Send text, image, or file

To send any data using Python, we use the send() method.
To send a text, add content to the method as shown:

import discord
from discord.ext import commands
TOKEN = "Insert Your token here"
discord_bot = commands.Bot(command_prefix='!')
@discord_bot.command(name='senddata')
async def send_data(ctx):
    await ctx.send("hello there")
discord_bot.run(TOKEN)

Output:

demo of sending text with discord bot in python

To send an image, add the image file to the method as shown below. The file my_file.png will be sent when the command !sendimg is used in the chat.

import discord
from discord.ext import commands
import os
os.chdir("Your file path")
TOKEN = "Insert Your token here"
discord_bot = commands.Bot(command_prefix='!')
@discord_bot.command(name='senddata')
async def send_img(ctx):
	await ctx.send("image",file=discord.File('Your file name'))
discord_bot.run(TOKEN)

Output:

demo of sending image with discord bot in python

To send a file or file-like object, you need to first open the file with the open() method and then use the send method to send data.

 

Send colored text

In Discord, it is possible to send a text in 8 different colors by code markup. It uses Highlight.js and solarized dark theme to achieve this effect.

import discord
from discord.ext import commands
TOKEN = "Insert Your token here"
discord_bot = commands.Bot(command_prefix='!')
@discord_bot.command(name='coloured_text')
async def test(ctx): 
    retStr = str("""```yaml\nThis is some colored Text```""") 
    await ctx.send(retStr)
discord_bot.run(TOKEN)

Output:

demo of sending text with discord bot in python

```yaml is the markup used to indicate the solarized cyan color. Below is a list of escape sequences or markup with their color codes that can be used to color your text.

Use the special characters such as ‘#’,'[‘, etc. wherever mentioned, to get the desired color. Note that the colored text is visible in the web application, but not the mobile application.

Default: #839496
```
Your text
```
Quote: #586e75
```bash
#Your text
```
Solarized Green: #859900
```diff
+ Your text
```
Solarized Cyan: #2aa198
```yaml
Your text
```
or
```cs
"Your text"
```
Solarized Blue: #268bd2
```ini
[Your text]
```
Solarized Yellow: #b58900
```fix
Your text
```
Solarized Orange: #cb4b16
```css
[Your text]
```
Solarized Red: #dc322f
```diff
-Your text
```

Simply replace the markup with markup for the desired color. The image below shows what the colors look like.
demonstrate how different colors are displayed

 

Get username from ID

To get the username based on the user id, we will use the fetch_user method. If the entire profile of a user is required then we can use the fetch_user_profile method.

import discord
from discord.ext import commands
TOKEN = "Insert Your token here"
discord_bot = commands.Bot(command_prefix='!')
@discord_bot.command(name='username')
async def user_name(ctx,user_id):
    user=await discord_bot.fetch_user(user_id)
    await ctx.send(user)
discord_bot.run(TOKEN)

Output:

demo of get username from user_id

 

Add user roles using the Discord bot

Roles are used to delegate permission to users within the server to help in management.
Roles can be implemented within large public servers such as in a learning-based server, roles like ‘student level 1’, ‘student level 2’, ‘teacher’, and ‘moderator’ can be assigned to users.
They can also be implemented in private servers with friends if such a need arises.

Each role has a different color and different permissions associated with it.
A user inherits the maximum permissions from each of the roles assigned to them.
The colors also work similarly, with the name of the member inheriting the role with the highest position in the hierarchy or with maximum permissions.
The roles have a linear hierarchy in Discord and can be managed from the ‘Manage role’ option in the ‘Roles’ tab.
Multiple roles with separate administrative powers can also be established so that the responsibilities of one user do not interfere with the other.

import discord
from discord import Member
from discord.ext.commands import has_permissions, MissingPermissions
from discord.ext import commands
TOKEN = "Insert Your token here"
intents=discord.Intents.all()
intents.members=True
bot = commands.Bot(command_prefix="!",intents=intents)
#code to add role
@bot.command()
@commands.has_permissions(manage_roles = True)
async def addRole(ctx, user : discord.Member, role:discord.Role):
    await user.add_roles(role)
    await ctx.send(f" Added {role} to {user.ention}")
@addRole.error
async def role_error(self,ctx,error):
    if isinstance(error, commands.MissingPermissions):
        await ctx.send("You are not authorized for this action")
bot.run(TOKEN)

To add a role, first, we use the commands to ensure that the user trying to add the role is authorized to do so.
We then use the add_roles method to add a function to the user.
Based on the requirements, you can allow multiple roles to be assigned to a user or use if-else statements to only allow one.
The role_error function is used to print an error message in case an unauthorized user tries to assign a role to another user.

Output:

demo of role assigning to users

User is assigned the ‘testoo’ role.

 

Send a Direct Message to Users

A lot of Discord servers are configured to send a welcome message via direct message when a user joins the server or to send a warning if they violate any of the server’s rules.
Let us look at a code that sends a DM when the designated command is used.

import discord
from discord.ext import commands
TOKEN ="Insert Your token here"
intents=discord.Intents.all()
client = commands.Bot(command_prefix='!', intents=intents)
@client.command()
async def drtmsg(ctx, user:discord.Member, *, message=None):
    message = "welcome"
    embed=discord.Embed(title=message)
    await user.send(embed=embed)
client.run(TOKEN)

This function will take input such as !drtmsg @User and then send the message “welcome” via private messaging to the selected user.

Output:

demonstration of sending DM to user with discord bot

 

Get members of a specific role and DM them

We first import the asyncio package for concurrent programming. We can use ctx.guild.roles to access the roles declared in the server.
The list of roles is fetched in user.roles which we can then traverse to find the correct role of the members and send them the desired message.

import asyncio 
import discord
from discord.ext import commands, tasks
Token ="Insert Your token here"
prefix="!"
intents=discord.Intents.all()
bot = commands.Bot(command_prefix=prefix,intents=intents)
@bot.command(name='dm') 
async def msg(ctx): 
    for guild in bot.guilds:  # all servers of the bot
        role = discord.utils.find(lambda r: r.name == 'Your role name', guild.roles)
        for member in guild.members:
            if role in member.roles:
                await member.send("Dm to role member")
bot.run(Token)

Output:

demo of members of role and send them a dm

 

Ban/Kick Members using Discord bot

To remove a member from the server you need to add the following code to your Python file.

import discord
from discord import Member
from discord.ext import commands
from discord.ext.commands import has_permissions, MissingPermissions
TOKEN = "Insert Your token here"
intents=discord.Intents.all()
discord_client = commands.Bot(command_prefix='!',intents=intents)
@discord_client.command()
@has_permissions(kick_members=True)
async def kick(ctx, member:discord.Member,*,reason=None):
    await member.kick(reason=reason)
    await ctx.send(f'The User {member} has been kicked from the server')
@kick.error
async def kick_error(ctx,error):
    if isinstance(error,commands.MissingPermissions):
        await ctx.send("You do not have required permission for the action performed")
discord_client.run(TOKEN)

First, import the member and required permissions method from the discord library to allow us to manage permissions and address a user.
We use @discord_client.command() to use the client object of the bot and inform it the code written ahead is a command.
@has_permissions is used to check whether the user performing the action is authorized to do so or not.
kick is the function that performs the actions of removing the given user from the server.

A dynamic reason can also be given, we have however simply stated the reason as “test”.
The second function kick_error is an exception function that is executed if the user is not authorized to kick another user out and the error message is printed.

A user that is kicked out is not permanently banned from the server.
To ensure that a user is added to the banned member list of the server add the function defined below.
This will ensure that the user cannot rejoin the server as long as they are present in the list.

import discord
from discord import Member
from discord.ext import commands
from discord.ext.commands import has_permissions, MissingPermissions
TOKEN = "Insert Your token here"
intents=discord.Intents.all()
discord_client = commands.Bot(command_prefix='!',intents=intents)
@discord_client.command()
@has_permissions(ban_members=True)
async def ban(ctx, member:discord.Member,*,reason=None):
    await member.ban(reason=reason)
    await ctx.send(f'The User {member} has been banned from the server')
@ban.error
async def ban_error(ctx,error):
    if isinstance(error,commands.MissingPermissions):
        await ctx.send("You do not have required permission for the action performed")
discord_client.run(TOKEN)

Output:

demo of ban user

 

Send a message at a specific time

To send a message at a specific time, the datetime library is required in addition to the discord library being used until now.

import discord
from datetime import datetime
client = discord.Client()
token = "Insert Your token here" 
channel_id=Insert Your channel id without quotes
def time_required():
    while True:
        current_time = datetime.now().strftime("%H:%M")
        if current_time == "Insert required time": 
            print("time reached")
            break
time_required()
@client.event
async def on_ready():
    print("bot:user ready == {0.user}".format(client))
    channel = client.get_channel(channel_id)
    await channel.send("timed message")
client.run(token)

The time_required function is created to establish when it is time to send the message. Until the required time is reached the bot will be in sleep mode.
Once it is time to send the message, the on_ready event will be executed, and the message “timed message” will be sent in the channel id mentioned in your code.

Output:

send message at time

 

Scheduling repeated actions

Sometimes, you may want your bot to do something at regular intervals, like sending a message every hour.

This can be done with tasks, which are a feature of discord.py that allow you to schedule asynchronous functions to be called at certain intervals.

Here’s an example:

from discord.ext import tasks

@tasks.loop(hours=1)
async def every_hour():
    channel = bot.get_channel(123456)  # Replace with your channel's ID
    await channel.send("It's a new hour!")

every_hour.start()

This task will send a message saying “It’s a new hour!” to the specified channel every hour.

 

Filter words

Sometimes users may use inappropriate language that the server moderators may want to delete to maintain the decorum of the server.
To avoid having to analyze every message one by one, a function can be written to automatically filter out and delete messages that contain a word from a pre-defined list of banned words.
Ensure that your bot has administration capabilities to run this code.

In the on_message function, if the message sent by the user contains a word from the list bannedWords, the message is deleted and a warning is sent to the user to refrain them from sending using such words again.
We then use process_commands to ensure the bot continues to process other commands assigned to it.

import discord
from discord.ext import commands, tasks
import re
Token ="Insert Your token here"
prefix="!"
bot = commands.Bot(command_prefix=prefix)
def msg_contains_word(msg, word):
    return re.search(fr'\b({word})\b', msg) is not None
@bot.command()
async def loadcog(ctx, cog):
    bot.load_extension(f"cogs.{cog}")
@bot.command()
async def unloadcog(ctx, cog):
    bot.unload_extension(f"cogs.{cog}")
bannedWords={"ret"}
@bot.event
async def on_message(message):
    messageAuthor = message.author
    if bannedWords != None and (isinstance(message.channel, discord.channel.DMChannel) == False):
        for bannedWord in bannedWords:
            if msg_contains_word(message.content.lower(), bannedWord):
                await message.delete()
                await message.channel.send(f"{messageAuthor.mention} Message contains  a banned word.")
    await bot.process_commands(message)
bot.run(Token)

Output:

demo of filter words

 

Conclusion

Discord is a powerful social media tool with a wide array of features. Bots additionally are extremely versatile and are capable to do a lot of things, some of which have been covered in this tutorial.
We can enable our bot to do a lot more things with the use of the correct combination of commands and events.

 

Further Reading

https://discordpy.readthedocs.io/en/latest/index.html

Leave a Reply

Your email address will not be published. Required fields are marked *