Visual Studio Code Discord Bot

by -144 views


Sentinel At present

This tutorial has a related video class created by the Existent Python team. Spotter it together with the written tutorial to deepen your understanding:
Creating a Discord Bot in Python

In a world where video games are and so important to so many people, advice and customs around games are vital. Discord offers both of those and more than in one well-designed package. In this tutorial, yous’ll learn how to make a Discord bot in Python so that you tin make the most of this fantastic platform.

By the end of this article you’ll learn:

  • What Discord is and why it’south so valuable
  • How to make a Discord bot through the Programmer Portal
  • How to create Discord connections
  • How to handle events
  • How to accept commands and validate assumptions
  • How to interact with various Discord APIs

You’ll begin by learning what Discord is and why it’due south valuable.

What Is Discord?

Discord is a voice and text communication platform for gamers.

Players, streamers, and developers use Discord to discuss games, reply questions, chat while they play, and much more. It even has a game shop, consummate with critical reviews and a subscription service. It is nearly a i-stop shop for gaming communities.

While there are many things you can build using Discord’s APIs, this tutorial volition focus on a item learning outcome: how to make a Discord bot in Python.

What Is a Bot?

Discord is growing in popularity. As such, automatic processes, such every bit banning inappropriate users and reacting to user requests are vital for a community to thrive and abound.

Automatic programs that wait and act like users and automatically reply to events and commands on Discord are chosen
bot users. Discord bot users (or just
bots) have almost unlimited applications.

For instance, allow’s say you’re managing a new Discord society and a user joins for the very first time. Excited, you may personally reach out to that user and welcome them to your community. You lot might likewise tell them near your channels or ask them to introduce themselves.

The user feels welcomed and enjoys the discussions that happen in your guild and they, in plough, invite friends.

Over time, your community grows then big that it’s no longer feasible to personally reach out to each new member, but you even so desire to ship them something to recognize them equally a new member of the society.

With a bot, information technology’s possible to automatically react to the new member joining your guild. You lot can even customize its behavior based on context and command how it interacts with each new user.

This is corking, but it’due south only i pocket-size example of how a bot can be useful. There are and so many opportunities for you to be creative with bots, once you know how to make them.

There are two key steps when you’re creating a bot:

  1. Create the bot user on Discord and register it with a gild.
  2. Write code that uses Discord’s APIs and implements your bot’southward behaviors.

In the side by side section, y’all’ll learn how to make a Discord bot in Discord’s Developer Portal.

How to Make a Discord Bot in the Developer Portal

Before you can dive into whatever Python code to handle events and create exciting automations, you need to first create a few Discord components:

  1. An business relationship
  2. An awarding
  3. A bot
  4. A guild

Y’all’ll larn more well-nigh each slice in the following sections.

Once you’ve created all of these components, you’ll tie them together past registering your bot with your lodge.

Yous can get started by heading to Discord’south Developer Portal.

Creating a Discord Business relationship

The first matter you lot’ll see is a landing page where you lot’ll need to either login, if yous accept an existing account, or create a new account:


If you need to create a new business relationship, then click on the
Register
button below
Login
and enter your business relationship information.

In one case you’re finished, you’ll exist redirected to the Developer Portal habitation page, where you’ll create your application.

Creating an Application

An
application
allows you lot to interact with Discord’s APIs by providing authentication tokens, designating permissions, and and then on.

To create a new application, select
New Awarding:

Discord: My Applications Screen

Side by side, you’ll be prompted to name your application. Select a name and click
Create:

Discord: Naming an Application

Congratulations! You made a Discord awarding. On the resulting screen, you tin can see data about your awarding:

Discord: Application General Information

Go along in heed that any program that interacts with Discord APIs requires a Discord awarding, non just bots. Bot-related APIs are but a subset of Discord’s total interface.

Yet, since this tutorial is virtually how to make a Discord bot, navigate to the
Bot
tab on the left-paw navigation list.

Creating a Bot

As you learned in the previous sections, a bot user is one that listens to and automatically reacts to certain events and commands on Discord.

For your code to actually be manifested on Discord, you’ll need to create a bot user. To exercise then, select
Add Bot:

Discord: Add Bot

Once you ostend that you desire to add together the bot to your awarding, y’all’ll meet the new bot user in the portal:

Discord: Bot Created Successfully

Notice that, by default, your bot user will inherit the proper name of your application. Instead, update the username to something more than bot-like, such equally
RealPythonTutorialBot, and
Save Changes:

Discord: Rename Bot

Now, the bot’s all set and set up to go, simply to where?

A bot user is not useful if information technology’due south not interacting with other users. Next, y’all’ll create a guild so that your bot can interact with other users.

Creating a Order

A
order
(or a
server, as information technology is often called in Discord’s user interface) is a specific group of channels where users congregate to chat.

For instance, say you want to create a space where users tin come together and talk most your latest game. You’d first by creating a society. So, in your guild, you could accept multiple channels, such equally:

  • General Discussion:
    A channel for users to talk most whatsoever they want
  • Spoilers, Beware:
    A channel for users who have finished your game to talk about all the end game reveals
  • Announcements:
    A channel for you to announce game updates and for users to discuss them

One time y’all’ve created your society, you’d invite other users to populate it.

And then, to create a social club, head to your Discord dwelling house page:

Discord: User Account Home Page

From this dwelling house page, you can view and add friends, direct letters, and guilds. From here, select the
+
icon on the left-hand side of the spider web page to
Add a Server:

Discord: Add Server

This volition present two options,
Create a server
and
Join a Server. In this case, select
Create a server
and enter a name for your social club:

Discord: Naming a Server

Once y’all’ve finished creating your guild, yous’ll be able to run across the users on the right-hand side and the channels on the left:

Discord: Newly Created Server

The final footstep on Discord is to annals your bot with your new lodge.

Calculation a Bot to a Guild

A bot can’t accept invites like a normal user tin. Instead, you’ll add your bot using the OAuth2 protocol.

To practice and so, head back to the Developer Portal and select the OAuth2 page from the left-hand navigation:

Discord: Application OAuth2

From this window, you lot’ll see the OAuth2 URL Generator.

This tool generates an potency URL that hits Discord’southward OAuth2 API and authorizes API admission using your application’due south credentials.

In this case, y’all’ll want to grant your application’s bot user access to Discord APIs using your awarding’s OAuth2 credentials.

To do this, curl downwardly and select
bot
from the
SCOPES
options and
Administrator
from
BOT PERMISSIONS:

Discord: Application Scopes and Bot Permissions

Now, Discord has generated your application’south dominance URL with the selected scope and permissions.

Select
Copy
abreast the URL that was generated for you, paste it into your browser, and select your guild from the dropdown options:

Discord: Add Bot to a Server

Click
Authorize, and you’re done!

If you get dorsum to your guild, so you’ll encounter that the bot has been added:

Discord: Bot Added to Guild

In summary, you’ve created:

  • An
    application
    that your bot will apply to authenticate with Discord’s APIs
  • A
    bot
    user that you’ll use to interact with other users and events in your guild
  • A
    guild
    in which your user business relationship and your bot user will be active
  • A
    Discord
    account with which you created everything else and that you’ll utilise to collaborate with your bot

Now, you know how to make a Discord bot using the Programmer Portal. Next comes the fun stuff: implementing your bot in Python!

How to Make a Discord Bot in Python

Since you’re learning how to make a Discord bot with Python, yous’ll exist using
discord.py.

discord.py
is a Python library that exhaustively implements Discord’s APIs in an efficient and Pythonic style. This includes utilizing Python’due south implementation of Async IO.

Brainstorm by installing
discord.py
with
pip:

            
            
              $
              pip install -U discord.py
            
          

At present that yous’ve installed
discord.py, you’ll use it to create your get-go connection to Discord!

Creating a Discord Connection

The first step in implementing your bot user is to create a connection to Discord. With
discord.py, y’all do this by creating an instance of
Client:

            
            
              # bot.py
              import
              os
              import
              discord
              from
              dotenv
              import
              load_dotenv
              load_dotenv
              ()
              TOKEN
              =
              os
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              client
              =
              discord
              .
              Client
              ()
              @client
              .
              event
              async
              def
              on_ready
              ():
              print
              (
              f
              '
              {
              customer
              .
              user
              }
              
                has connected to Discord!'
              )
              client
              .
              run
              (
              TOKEN
              )
            
          

A
Client
is an object that represents a connection to Discord. A
Client
handles events, tracks land, and generally interacts with Discord APIs.

Here, you’ve created a
Client
and implemented its
on_ready()
event handler, which handles the event when the
Client
has established a connection to Discord and it has finished preparing the data that Discord has sent, such as login state, society and channel data, and more.

In other words,
on_ready()
will be called (and your message will be printed) once
customer
is ready for further action. You’ll learn more nigh upshot handlers afterward in this article.

When you’re working with secrets such every bit your Discord token, it’due south practiced do to read it into your programme from an environment variable. Using environs variables helps you:

  • Avoid putting the secrets into source control
  • Use different variables for development and production environments without changing your code

While y’all could
export DISCORD_TOKEN={your-bot-token}, an easier solution is to relieve a
.env
file on all machines that will be running this code. This is not only easier, since you won’t take to
export
your token every time you clear your shell, simply it too protects yous from storing your secrets in your shell’southward history.

Create a file named
.env
in the same directory as
bot.py:

            
            # .env DISCORD_TOKEN={your-bot-token}
            
          

Y’all’ll demand to replace
{your-bot-token}
with your bot’s token, which you can get by going back to the
Bot
page on the Developer Portal and clicking
Copy
under the
TOKEN
section:

Discord: Copy Bot Token

Looking back at the
bot.py
code, you’ll observe a library called
dotenv. This library is handy for working with
.env
files.
load_dotenv()
loads environment variables from a
.env
file into your shell’s surroundings variables and so that you tin can use them in your code.

Install
dotenv
with
pip:

            
            
              $
              pip install -U python-dotenv
            
          

Finally,
client.run()
runs your
Client
using your bot’s token.

Now that you lot’ve set upwardly both
bot.py
and
.env, you lot can run your lawmaking:

            
            
              $
              python bot.py
              RealPythonTutorialBot#9643 has connected to Discord!
            
          

Keen! Your
Client
has connected to Discord using your bot’s token. In the next section, you’ll build on this
Client
by interacting with more Discord APIs.

Interacting With Discord APIs

Using a
Client, you accept admission to a wide range of Discord APIs.

For example, permit’s say you wanted to write the name and identifier of the guild that you registered your bot user with to the console.

Outset, you lot’ll need to add a new surround variable:

            
            # .env DISCORD_TOKEN={your-bot-token}
              DISCORD_GUILD={your-society-proper noun}
              
            
          

Don’t forget that you’ll need to supercede the two placeholders with bodily values:

  1. {your-bot-token}
  2. {your-lodge-name}

Think that Discord calls
on_ready(), which y’all used before, once the
Customer
has made the connection and prepared the data. So, you tin rely on the guild information being bachelor inside
on_ready():

            
            
              # bot.py
              import
              os
              import
              discord
              from
              dotenv
              import
              load_dotenv
              load_dotenv
              ()
              TOKEN
              =
              bone
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              GUILD
              =
              os
              .
              getenv
              (
              'DISCORD_GUILD'
              )
              client
              =
              discord
              .
              Customer
              ()
              @client
              .
              upshot
              async
              def
              on_ready
              ():
              for
              guild
              in
              client
              .
              guilds
              :
              if
              guild
              .
              proper name
              ==
              GUILD
              :
              break
              print
              (
              f
              '
              {
              client
              .
              user
              }
              
                is connected to the following guild:
              \n
              '
              f
              '
              {
              guild
              .
              name
              }
              (id:
              
              {
              gild
              .
              id
              }
              )'
              )
              customer
              .
              run
              (
              TOKEN
              )
            
          

Here, you looped through the guild information that Discord has sent
client, namely
client.guilds. So, y’all found the guild with the matching name and printed a formatted string to
stdout.

Run the program to see the results:

            
            
              $
              python bot.py
              RealPythonTutorialBot#9643 is connected to the following gild:
              RealPythonTutorialServer(id: 571759877328732195)
            
          

Great! You can see the name of your bot, the name of your server, and the server’s identification number.

Some other interesting bit of data you tin pull from a club is the listing of users who are members of the order:

            
            
              # bot.py
              import
              os
              import
              discord
              from
              dotenv
              import
              load_dotenv
              load_dotenv
              ()
              TOKEN
              =
              os
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              GUILD
              =
              os
              .
              getenv
              (
              'DISCORD_GUILD'
              )
              customer
              =
              discord
              .
              Client
              ()
              @client
              .
              issue
              async
              def
              on_ready
              ():
              for
              guild
              in
              client
              .
              guilds
              :
              if
              guild
              .
              proper name
              ==
              Club
              :
              break
              print
              (
              f
              '
              {
              client
              .
              user
              }
              
                is continued to the following order:
              \n
              '
              f
              '
              {
              guild
              .
              name
              }
              (id:
              
              {
              guild
              .
              id
              }
              )
              \n
              '
              )
              members
              =
              '
              \northward
              
                - '
              .
              join
              ([
              fellow member
              .
              name
              for
              fellow member
              in
              guild
              .
              members
              ])
              print
              (
              f
              'Guild Members:
              \due north
              
                -
              
              {
              members
              }
              '
              )
              client
              .
              run
              (
              TOKEN
              )
            
          

Past looping through
guild.members, you pulled the names of all of the members of the order and printed them with a formatted string.

When you lot run the program, yous should see at least the proper noun of the business relationship you created the guild with and the proper name of the bot user itself:

            
            
              $
              python bot.py
              RealPythonTutorialBot#9643 is connected to the following guild:
              RealPythonTutorialServer(id: 571759877328732195)
              Lodge Members:
              
                - aronq2
              
                - RealPythonTutorialBot
            
          

These examples barely scratch the surface of the APIs bachelor on Discord, be sure to bank check out their documentation to see all that they have to offering.

Next, you’ll learn about some utility functions and how they can simplify these examples.

Using Utility Functions

Let’s take another look at the example from the final section where you printed the proper name and identifier of the bot’s club:

            
            
              # bot.py
              import
              os
              import
              discord
              from
              dotenv
              import
              load_dotenv
              load_dotenv
              ()
              TOKEN
              =
              os
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              Lodge
              =
              os
              .
              getenv
              (
              'DISCORD_GUILD'
              )
              client
              =
              discord
              .
              Client
              ()
              @customer
              .
              event
              async
              def
              on_ready
              ():
              for
              guild
              in
              client
              .
              guilds
              :
              if
              guild
              .
              name
              ==
              Club
              :
              pause
              print
              (
              f
              '
              {
              client
              .
              user
              }
              
                is connected to the post-obit society:
              \n
              '
              f
              '
              {
              social club
              .
              name
              }
              (id:
              
              {
              guild
              .
              id
              }
              )'
              )
              client
              .
              run
              (
              TOKEN
              )
            
          

Yous could clean up this code by using some of the utility functions available in
discord.py.

discord.utils.find()
is one utility that tin improve the simplicity and readability of this code past replacing the
for
loop with an intuitive, bathetic function:

            
            
              # bot.py
              import
              bone
              import
              discord
              from
              dotenv
              import
              load_dotenv
              load_dotenv
              ()
              TOKEN
              =
              os
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              GUILD
              =
              bone
              .
              getenv
              (
              'DISCORD_GUILD'
              )
              client
              =
              discord
              .
              Client
              ()
              @client
              .
              event
              async
              def
              on_ready
              ():
              society
              =
              discord
              .
              utils
              .
              find
              (
              lambda
              g
              :
              g
              .
              name
              ==
              GUILD
              ,
              client
              .
              guilds
              )
              print
              (
              f
              '
              {
              client
              .
              user
              }
              
                is connected to the following guild:
              \n
              '
              f
              '
              {
              club
              .
              name
              }
              (id:
              
              {
              lodge
              .
              id
              }
              )'
              )
              customer
              .
              run
              (
              TOKEN
              )
            
          

find()
takes a function, chosen a
predicate, which identifies some characteristic of the chemical element in the iterable that y’all’re looking for. Here, you used a particular type of bearding part, called a lambda, as the predicate.

In this instance, you’re trying to find the social club with the same proper noun as the i yous stored in the
DISCORD_GUILD
environment variable. Once
find()
locates an element in the iterable that satisfies the predicate, it will return the chemical element. This is essentially equivalent to the
interruption
statement in the previous case, simply cleaner.

discord.py
has even abstracted this concept one pace further with the
get()
utility:

            
            
              # bot.py
              import
              os
              import
              discord
              from
              dotenv
              import
              load_dotenv
              load_dotenv
              ()
              TOKEN
              =
              os
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              GUILD
              =
              os
              .
              getenv
              (
              'DISCORD_GUILD'
              )
              client
              =
              discord
              .
              Customer
              ()
              @client
              .
              upshot
              async
              def
              on_ready
              ():
              guild
              =
              discord
              .
              utils
              .
              go
              (
              customer
              .
              guilds
              ,
              proper noun
              =
              GUILD
              )
              impress
              (
              f
              '
              {
              customer
              .
              user
              }
              
                is connected to the following guild:
              \n
              '
              f
              '
              {
              gild
              .
              name
              }
              (id:
              
              {
              guild
              .
              id
              }
              )'
              )
              client
              .
              run
              (
              TOKEN
              )
            
          

get()
takes the iterable and some keyword arguments. The keyword arguments represent attributes of the elements in the iterable that must all be satisfied for
become()
to return the element.

In this example, you’ve identified
name=Club
every bit the attribute that must be satisfied.

Now that you’ve learned the basics of interacting with APIs, you’ll dive a little deeper into the function that you’ve been using to access them:
on_ready().

Responding to Events

You already learned that
on_ready()
is an event. In fact, y’all might have noticed that it is identified as such in the lawmaking by the
client.result
decorator.

But what is an event?

An
event
is something that happens on Discord that yous tin utilise to trigger a reaction in your code. Your lawmaking will heed for then answer to events.

Using the instance you’ve seen already, the
on_ready()
effect handler handles the event that the
Client
has made a connectedness to Discord and prepared its response data.

And so, when Discord fires an consequence,
discord.py
will road the event data to the corresponding event handler on your connected
Client.

There are two means in
discord.py
to implement an event handler:

  1. Using the
    client.event
    decorator
  2. Creating a bracket of
    Client
    and overriding its handler methods

You lot already saw the implementation using the decorator. Next, take a look at how to subclass
Customer:

            
            
              # bot.py
              import
              os
              import
              discord
              from
              dotenv
              import
              load_dotenv
              load_dotenv
              ()
              TOKEN
              =
              os
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              class
              CustomClient
              (
              discord
              .
              Client
              ):
              async
              def
              on_ready
              (
              self
              ):
              print
              (
              f
              '
              {
              self
              .
              user
              }
              
                has connected to Discord!'
              )
              client
              =
              CustomClient
              ()
              client
              .
              run
              (
              TOKEN
              )
            
          

Hither, but like before, you’ve created a
client
variable and called
.run()
with your Discord token. The actual
Client
is different, however. Instead of using the normal base class,
client
is an instance of
CustomClient, which has an overridden
on_ready()
function.

There is no departure betwixt the two implementation styles of events, just this tutorial will primarily use the decorator version considering it looks similar to how you implement
Bot
commands, which is a topic you lot’ll comprehend in a bit.

Now that you’ve learned how to create an event handler, let’s walk through some different examples of handlers you can create.

Welcoming New Members

Previously, you saw the example of responding to the result where a member joins a guild. In that example, your bot user could ship them a message, welcoming them to your Discord community.

Now, you’ll implement that behavior in your
Client, using event handlers, and verify its behavior in Discord:

              
              
                # bot.py
                import
                os
                import
                discord
                from
                dotenv
                import
                load_dotenv
                load_dotenv
                ()
                TOKEN
                =
                os
                .
                getenv
                (
                'DISCORD_TOKEN'
                )
                customer
                =
                discord
                .
                Client
                ()
                @client
                .
                effect
                async
                def
                on_ready
                ():
                print
                (
                f
                '
                {
                client
                .
                user
                .
                name
                }
                
                  has connected to Discord!'
                )
                @client
                .
                outcome
                async
                def
                on_member_join
                (
                member
                ):
                await
                member
                .
                create_dm
                ()
                await
                fellow member
                .
                dm_channel
                .
                ship
                (
                f
                'Hi
                
                {
                member
                .
                name
                }
                , welcome to my Discord server!'
                )
                client
                .
                run
                (
                TOKEN
                )
              
            

Similar before, you lot handled the
on_ready()
event by printing the bot user’south name in a formatted string. New, nevertheless, is the implementation of the
on_member_join()
consequence handler.

on_member_join(), as its name suggests, handles the upshot of a new member joining a guild.

In this example, you used
member.create_dm()
to create a direct message channel. And then, yous used that channel to
.send()
a direct message to that new member.

At present, let’s exam out your bot’south new behavior.

First, run your new version of
bot.py
and wait for the
on_ready()
event to burn down, logging your bulletin to
stdout:

              
              
                $
                python bot.py
                RealPythonTutorialBot has connected to Discord!
              
            

At present, head over to Discord, log in, and navigate to your guild by selecting it from the left-hand side of the screen:

Discord: Navigate to Server

Select
Invite People
only beside the society list where y’all selected your guild. Check the box that says
Set this link to never expire
and copy the link:

Discord: Copy Invite Link

Now, with the invite link copied, create a new business relationship and join the guild using your invite link:

Discord: Accept Invite

Offset, you’ll see that Discord introduced you lot to the guild by default with an automated bulletin. More importantly though, notice the badge on the left-mitt side of the screen that notifies you of a new message:

Discord: Direct Message Notification

When y’all select it, you lot’ll see a individual message from your bot user:

Discord: Direct Message

Perfect! Your bot user is at present interacting with other users with minimal code.

Next, you lot’ll learn how to respond to specific user messages in the chat.

Responding to Messages

Let’south add on to the previous functionality of your bot by handling the
on_message()
event.

on_message()
occurs when a bulletin is posted in a channel that your bot has access to. In this example, you’ll answer to the message
'99!'
with a one-liner from the television testify Brooklyn Ix-Nine:

              
              
                @client
                .
                consequence
                async
                def
                on_message
                (
                bulletin
                ):
                if
                message
                .
                author
                ==
                customer
                .
                user
                :
                return
                brooklyn_99_quotes
                =
                [
                'I
                \'
                m the human being form of the 💯 emoji.'
                ,
                'Bingpot!'
                ,
                (
                'Cool. Absurd cool absurd cool cool cool absurd, '
                'no doubt no doubt no doubt no doubt.'
                ),
                ]
                if
                message
                .
                content
                ==
                '99!'
                :
                response
                =
                random
                .
                choice
                (
                brooklyn_99_quotes
                )
                wait
                message
                .
                channel
                .
                transport
                (
                response
                )
              
            

The bulk of this event handler looks at the
message.content, checks to run across if it’southward equal to
'99!', and responds by sending a random quote to the bulletin’s channel if information technology is.

The other piece is an important one:

              
              
                if
                bulletin
                .
                writer
                ==
                client
                .
                user
                :
                render
              
            

Because a
Client
can’t tell the deviation between a bot user and a normal user account, your
on_message()
handler should protect against a potentially recursive case where the bot sends a bulletin that it might, itself, handle.

To illustrate, let’southward say you lot want your bot to listen for users telling each other
'Happy Birthday'. You could implement your
on_message()
handler similar this:

              
              
                @customer
                .
                event
                async
                def
                on_message
                (
                message
                ):
                if
                'happy birthday'
                in
                bulletin
                .
                content
                .
                lower
                ():
                await
                bulletin
                .
                channel
                .
                send
                (
                'Happy Birthday! 🎈🎉'
                )
              
            

Aside from the potentially spammy nature of this event handler, it besides has a devastating side result. The message that the bot responds with contains the same message information technology’s going to handle!

And so, if ane person in the channel tells another “Happy Birthday,” and then the bot will too chime in… again… and again… and over again:

Discord: Happy Birthday Message Repetition

That’due south why it’southward important to compare the
message.author
to the
client.user
(your bot user), and ignore any of its own messages.

Then, let’due south fix
bot.py:

              
              
                # bot.py
                import
                os
                import
                random
                import
                discord
                from
                dotenv
                import
                load_dotenv
                load_dotenv
                ()
                TOKEN
                =
                os
                .
                getenv
                (
                'DISCORD_TOKEN'
                )
                customer
                =
                discord
                .
                Customer
                ()
                @customer
                .
                event
                async
                def
                on_ready
                ():
                print
                (
                f
                '
                {
                customer
                .
                user
                .
                proper name
                }
                
                  has connected to Discord!'
                )
                @client
                .
                effect
                async
                def
                on_member_join
                (
                member
                ):
                await
                fellow member
                .
                create_dm
                ()
                await
                member
                .
                dm_channel
                .
                transport
                (
                f
                'Hi
                
                {
                fellow member
                .
                proper noun
                }
                , welcome to my Discord server!'
                )
                @client
                .
                event
                async
                def
                on_message
                (
                bulletin
                ):
                if
                message
                .
                writer
                ==
                client
                .
                user
                :
                render
                brooklyn_99_quotes
                =
                [
                'I
                \'
                thou the human being course of the 💯 emoji.'
                ,
                'Bingpot!'
                ,
                (
                'Cool. Cool cool absurd absurd absurd absurd cool, '
                'no dubiousness no doubt no doubt no doubt.'
                ),
                ]
                if
                message
                .
                content
                ==
                '99!'
                :
                response
                =
                random
                .
                pick
                (
                brooklyn_99_quotes
                )
                await
                message
                .
                channel
                .
                ship
                (
                response
                )
                client
                .
                run
                (
                TOKEN
                )
              
            

Don’t forget to
import random
at the top of the module, since the
on_message()
handler utilizes
random.choice().

Run the program:

              
              
                $
                python bot.py
                RealPythonTutorialBot has continued to Discord!
              
            

Finally, caput over to Discord to test it out:

Discord: Quotes From Brooklyn Nine-Nine

Great! At present that you lot’ve seen a few different ways to handle some common Discord events, y’all’ll learn how to deal with errors that event handlers may raise.

Treatment Exceptions

As you’ve seen already,
discord.py
is an issue-driven organisation. This focus on events extends even to exceptions. When one issue handler raises an
Exception, Discord calls
on_error().

The default behavior of
on_error()
is to write the error message and stack trace to
stderr. To test this, add a special message handler to
on_message():

              
              
                # bot.py
                import
                os
                import
                random
                import
                discord
                from
                dotenv
                import
                load_dotenv
                load_dotenv
                ()
                TOKEN
                =
                os
                .
                getenv
                (
                'DISCORD_TOKEN'
                )
                client
                =
                discord
                .
                Client
                ()
                @client
                .
                consequence
                async
                def
                on_ready
                ():
                impress
                (
                f
                '
                {
                client
                .
                user
                .
                name
                }
                
                  has continued to Discord!'
                )
                @customer
                .
                event
                async
                def
                on_member_join
                (
                fellow member
                ):
                await
                member
                .
                create_dm
                ()
                await
                member
                .
                dm_channel
                .
                send
                (
                f
                'Hi
                
                {
                fellow member
                .
                proper noun
                }
                , welcome to my Discord server!'
                )
                @client
                .
                effect
                async
                def
                on_message
                (
                message
                ):
                if
                message
                .
                author
                ==
                client
                .
                user
                :
                return
                brooklyn_99_quotes
                =
                [
                'I
                \'
                m the human class of the 💯 emoji.'
                ,
                'Bingpot!'
                ,
                (
                'Cool. Cool absurd cool cool cool cool absurd, '
                'no doubt no doubt no doubt no doubt.'
                ),
                ]
                if
                message
                .
                content
                ==
                '99!'
                :
                response
                =
                random
                .
                selection
                (
                brooklyn_99_quotes
                )
                await
                bulletin
                .
                channel
                .
                send
                (
                response
                )
                
                  elif
                  bulletin
                  .
                  content
                  ==
                  'enhance-exception'
                  :
                
                
                  raise
                  discord
                  .
                  DiscordException
                
                customer
                .
                run
                (
                TOKEN
                )
              
            

The new
raise-exception
message handler allows you to raise a
DiscordException
on command.

Run the program and blazon
raise-exception
into the Discord channel:

Discord: Raise Exception Message

Y’all should at present come across the
Exception
that was raised past your
on_message()
handler in the panel:

              
              
                $
                python bot.py
                RealPythonTutorialBot has continued to Discord!
                Ignoring exception in on_message
                Traceback (most recent call last):
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.seven/site-packages/discord/client.py", line 255, in _run_event
                
                  await coro(*args, **kwargs)
                
                  File "bot.py", line 42, in on_message
                
                  raise discord.DiscordException
                discord.errors.DiscordException
              
            

The exception was caught by the default fault handler, so the output contains the message
Ignoring exception in on_message. Let’s set up that by handling that particular error. To do then, you’ll catch the
DiscordException
and write it to a file instead.

The
on_error()
issue handler takes the
upshot
equally the first argument. In this case, we await the
event
to be
'on_message'. Information technology also accepts
*args
and
**kwargs
as flexible, positional and keyword arguments passed to the original event handler.

So, since
on_message()
takes a single argument,
message, nosotros await
args[0]
to exist the
message
that the user sent in the Discord channel:

              
              
                @client
                .
                effect
                async
                def
                on_error
                (
                event
                ,
                *
                args
                ,
                **
                kwargs
                ):
                with
                open
                (
                'err.log'
                ,
                'a'
                )
                every bit
                f
                :
                if
                issue
                ==
                'on_message'
                :
                f
                .
                write
                (
                f
                'Unhandled bulletin:
                
                {
                args
                [
                0
                ]
                }
                \n
                '
                )
                else
                :
                raise
              
            

If the
Exception
originated in the
on_message()
upshot handler, you
.write()
a formatted string to the file
err.log. If some other outcome raises an
Exception, then we simply want our handler to re-heighten the exception to invoke the default behavior.

Run
bot.py
and send the
raise-exception
message again to view the output in
err.log:

              
              
                $
                true cat err.log
                Unhandled message: <Message id=573845548923224084 pinned=False author=<Fellow member id=543612676807327754 name='alexronquillo' discriminator='0933' bot=Imitation nick=None guild=<Guild id=571759877328732195 proper name='RealPythonTutorialServer' chunked=True>>>
              
            

Instead of only a stack trace, you have a more informative error, showing the
message
that caused
on_message()
to enhance the
DiscordException, saved to a file for longer persistence.

At present that you lot accept some experience handling unlike events and interacting with Discord APIs, you’ll learn almost a subclass of
Client
called
Bot, which implements some handy, bot-specific functionality.

Connecting a Bot

A
Bot
is a subclass of
Client
that adds a little bit of extra functionality that is useful when you lot’re creating bot users. For example, a
Bot
tin handle events and commands, invoke validation checks, and more.

Before you get into the features specific to
Bot, convert
bot.py
to employ a
Bot
instead of a
Client:

            
            
              # bot.py
              import
              os
              import
              random
              from
              dotenv
              import
              load_dotenv
              # 1
              from
              discord.ext
              import
              commands
              load_dotenv
              ()
              TOKEN
              =
              bone
              .
              getenv
              (
              'DISCORD_TOKEN'
              )
              # 2
              bot
              =
              commands
              .
              Bot
              (
              command_prefix
              =
              '!'
              )
              @bot
              .
              outcome
              async
              def
              on_ready
              ():
              impress
              (
              f
              '
              {
              bot
              .
              user
              .
              name
              }
              
                has connected to Discord!'
              )
              bot
              .
              run
              (
              TOKEN
              )
            
          

As you lot can run across,
Bot
can handle events the same style that
Client
does. All the same, find the differences between
Client
and
Bot:

  1. Bot
    is imported from the
    discord.ext.commands
    module.
  2. The
    Bot
    initializer requires a
    command_prefix, which you’ll learn more than about in the next section.

The extensions library,
ext, offers several interesting components to help you lot create a Discord
Bot. One such component is the
Command.

Using
Bot
Commands

In full general terms, a
command
is an order that a user gives to a bot so that it will do something. Commands are unlike from events considering they are:

  • Arbitrarily defined
  • Directly called past the user
  • Flexible, in terms of their interface

In technical terms, a

Command

is an object that wraps a role that is invoked by a text command in Discord. The text command must start with the
command_prefix, defined by the
Bot
object.

Let’southward take a expect at an old consequence to meliorate empathise what this looks like:

              
              
                # bot.py
                import
                os
                import
                random
                import
                discord
                from
                dotenv
                import
                load_dotenv
                load_dotenv
                ()
                TOKEN
                =
                os
                .
                getenv
                (
                'DISCORD_TOKEN'
                )
                client
                =
                discord
                .
                Customer
                ()
                @client
                .
                issue
                async
                def
                on_message
                (
                message
                ):
                if
                message
                .
                author
                ==
                client
                .
                user
                :
                return
                brooklyn_99_quotes
                =
                [
                'I
                \'
                m the homo form of the 💯 emoji.'
                ,
                'Bingpot!'
                ,
                (
                'Cool. Cool cool cool absurd absurd cool cool, '
                'no doubt no doubt no doubt no dubiety.'
                ),
                ]
                if
                message
                .
                content
                ==
                '99!'
                :
                response
                =
                random
                .
                choice
                (
                brooklyn_99_quotes
                )
                await
                message
                .
                channel
                .
                transport
                (
                response
                )
                client
                .
                run
                (
                TOKEN
                )
              
            

Here, you created an
on_message()
event handler, which receives the
message
string and compares it to a pre-defined option:
'99!'.

Using a
Control, you can catechumen this example to exist more specific:

              
              
                # bot.py
                import
                os
                import
                random
                from
                discord.ext
                import
                commands
                from
                dotenv
                import
                load_dotenv
                load_dotenv
                ()
                TOKEN
                =
                os
                .
                getenv
                (
                'DISCORD_TOKEN'
                )
                bot
                =
                commands
                .
                Bot
                (
                command_prefix
                =
                '!'
                )
                @bot
                .
                command
                (
                name
                =
                '99'
                )
                async
                def
                nine_nine
                (
                ctx
                ):
                brooklyn_99_quotes
                =
                [
                'I
                \'
                g the homo form of the 💯 emoji.'
                ,
                'Bingpot!'
                ,
                (
                'Cool. Cool cool cool cool absurd absurd cool, '
                'no doubt no doubt no incertitude no doubt.'
                ),
                ]
                response
                =
                random
                .
                option
                (
                brooklyn_99_quotes
                )
                await
                ctx
                .
                send
                (
                response
                )
                bot
                .
                run
                (
                TOKEN
                )
              
            

There are several important characteristics to sympathise about using
Command:

  1. Instead of using
    bot.upshot
    similar before, you lot use
    bot.control(), passing the invocation command (name) as its argument.

  2. The role volition now only be called when
    !99
    is mentioned in chat. This is different than the
    on_message()
    event, which was executed whatever time a user sent a bulletin, regardless of the content.

  3. The command must be prefixed with the assertion point (!) because that’s the
    command_prefix
    that you lot defined in the initializer for your
    Bot.

  4. Any
    Control
    function (technically chosen a
    callback) must accept at least one parameter, called
    ctx, which is the
    Context
    surrounding the invoked
    Command.

A
Context
holds data such every bit the channel and lodge that the user called the
Control
from.

Run the program:

With your bot running, you lot can at present caput to Discord to effort out your new command:

Discord: Brooklyn Nine-Nine Command

From the user’southward point of view, the applied divergence is that the prefix helps formalize the command, rather than merely reacting to a particular
on_message()
outcome.

This comes with other great benefits every bit well. For instance, you lot tin can invoke the
!assistance
command to meet all the commands that your
Bot
handles:

Discord: Help Command

If y’all want to add a description to your control so that the
help
message is more informative, simply laissez passer a
assistance
clarification to the
.command()
decorator:

              
              
                # bot.py
                import
                bone
                import
                random
                from
                discord.ext
                import
                commands
                from
                dotenv
                import
                load_dotenv
                load_dotenv
                ()
                TOKEN
                =
                os
                .
                getenv
                (
                'DISCORD_TOKEN'
                )
                bot
                =
                commands
                .
                Bot
                (
                command_prefix
                =
                '!'
                )
                @bot
                .
                control
                (
                name
                =
                '99'
                ,
                help
                =
                'Responds with a random quote from Brooklyn 99'
                )
                async
                def
                nine_nine
                (
                ctx
                ):
                brooklyn_99_quotes
                =
                [
                'I
                \'
                m the human class of the 💯 emoji.'
                ,
                'Bingpot!'
                ,
                (
                'Cool. Cool absurd cool cool absurd absurd cool, '
                'no doubt no doubt no doubt no doubt.'
                ),
                ]
                response
                =
                random
                .
                choice
                (
                brooklyn_99_quotes
                )
                await
                ctx
                .
                send
                (
                response
                )
                bot
                .
                run
                (
                TOKEN
                )
              
            

Now, when the user invokes the
!aid
control, your bot will present a description of your command:

Discord: Informative Help Description

Keep in listen that all of this functionality exists only for the
Bot
subclass, not the
Client
superclass.

Control
has another useful functionality: the power to employ a
Converter
to change the types of its arguments.

Converting Parameters Automatically

Another benefit of using commands is the ability to
convert
parameters.

Sometimes, you require a parameter to be a certain type, but arguments to a
Command
function are, by default, strings. A
Converter
lets you convert those parameters to the blazon that you lot await.

For example, if yous want to build a
Command
for your bot user to simulate rolling some die (knowing what you’ve learned so far), you might define information technology like this:

              
              
                @bot
                .
                command
                (
                name
                =
                'roll_dice'
                ,
                help
                =
                'Simulates rolling dice.'
                )
                async
                def
                ringlet
                (
                ctx
                ,
                number_of_dice
                ,
                number_of_sides
                ):
                dice
                =
                [
                str
                (
                random
                .
                selection
                (
                range
                (
                ane
                ,
                number_of_sides
                +
                1
                )))
                for
                _
                in
                range
                (
                number_of_dice
                )
                ]
                await
                ctx
                .
                send
                (
                ', '
                .
                join
                (
                dice
                ))
              
            

You divers
roll
to take 2 parameters:

  1. The number of dice to roll
  2. The number of sides per die

And then, you lot decorated it with
.control()
so that yous can invoke information technology with the
!roll_dice
control. Finally, you
.send()
the results in a message back to the
channel.

While this looks right, it isn’t. Unfortunately, if you run
bot.py, and invoke the
!roll_dice
command in your Discord channel, you’ll run across the following fault:

              
              
                $
                python bot.py
                Ignoring exception in command roll_dice:
                Traceback (most recent call terminal):
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 63, in wrapped
                
                  ret = look coro(*args, **kwargs)
                
                  File "bot.py", line twoscore, in curl
                
                  for _ in range(number_of_dice)
                TypeError: 'str' object cannot be interpreted as an integer
                The above exception was the directly cause of the following exception:
                Traceback (virtually contempo telephone call final):
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.seven/site-packages/discord/ext/commands/bot.py", line 860, in invoke
                
                  await ctx.command.invoke(ctx)
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 698, in invoke
                
                  await injected(*ctx.args, **ctx.kwargs)
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 72, in wrapped
                
                  enhance CommandInvokeError(exc) from exc
                discord.ext.commands.errors.CommandInvokeError: Control raised an exception: TypeError: 'str' object cannot be interpreted as an integer
              
            

In other words,
range()
can’t accept a
str
as an argument. Instead, information technology must be an
int. While you could cast each value to an
int, in that location is a better mode: you tin utilize a
Converter
.

In
discord.py, a
Converter
is defined using Python three’due south role annotations:

              
              
                @bot
                .
                control
                (
                name
                =
                'roll_dice'
                ,
                help
                =
                'Simulates rolling dice.'
                )
                async
                def
                roll
                (
                ctx
                ,
                number_of_dice
                :
                int
                ,
                number_of_sides
                :
                int
                ):
                dice
                =
                [
                str
                (
                random
                .
                selection
                (
                range
                (
                1
                ,
                number_of_sides
                +
                1
                )))
                for
                _
                in
                range
                (
                number_of_dice
                )
                ]
                await
                ctx
                .
                send
                (
                ', '
                .
                join
                (
                dice
                ))
              
            

You added
: int
annotations to the two parameters that y’all expect to be of blazon
int. Try the command again:

Discord: Bot Dice-Rolling Command

With that little change, your control works! The deviation is that you’re now converting the command arguments to
int, which makes them compatible with your function’southward logic.

Next, yous’ll acquire about the
Check
object and how it can improve your commands.

Checking Command Predicates

A
Bank check
is a predicate that is evaluated earlier a
Command
is executed to ensure that the
Context
surrounding the
Control
invocation is valid.

In an before example, you did something similar to verify that the user who sent a message that the bot handles was non the bot user, itself:

              
              
                if
                message
                .
                author
                ==
                customer
                .
                user
                :
                return
              
            

The
commands
extension provides a cleaner and more usable mechanism for performing this kind of check, namely using
Check
objects.

To demonstrate how this works, assume you want to support a command
!create-channel <channel_name>
that creates a new channel. Even so, y’all but want to allow administrators the ability to create new channels with this command.

First, yous’ll need to create a new fellow member role in the admin. Get into the Discord guild and select the
{Server Name} → Server Settings
carte:

Discord: Server Settings Screen

Then, select
Roles
from the left-mitt navigation list:

Discord: Navigate to Roles

Finally select the
+
sign next to
ROLES
and enter the name
admin
and select
Relieve Changes:

Discord: Create New Admin Role

Now, you’ve created an
admin
role that you can assign to particular users. Next, you’ll update
bot.py
to
Bank check
the user’due south office before allowing them to initiate the command:

              
              
                # bot.py
                import
                os
                import
                discord
                from
                discord.ext
                import
                commands
                from
                dotenv
                import
                load_dotenv
                load_dotenv
                ()
                TOKEN
                =
                bone
                .
                getenv
                (
                'DISCORD_TOKEN'
                )
                bot
                =
                commands
                .
                Bot
                (
                command_prefix
                =
                '!'
                )
                @bot
                .
                command
                (
                name
                =
                'create-aqueduct'
                )
                @commands
                .
                has_role
                (
                'admin'
                )
                async
                def
                create_channel
                (
                ctx
                ,
                channel_name
                =
                'real-python'
                ):
                guild
                =
                ctx
                .
                social club
                existing_channel
                =
                discord
                .
                utils
                .
                become
                (
                guild
                .
                channels
                ,
                proper name
                =
                channel_name
                )
                if
                non
                existing_channel
                :
                impress
                (
                f
                'Creating a new aqueduct:
                
                {
                channel_name
                }
                '
                )
                wait
                guild
                .
                create_text_channel
                (
                channel_name
                )
                bot
                .
                run
                (
                TOKEN
                )
              
            

In
bot.py, you accept a new
Command
part, called
create_channel()
which takes an optional
channel_name
and creates that channel.
create_channel()
is also busy with a
Check
called
has_role().

You too use
discord.utils.get()
to ensure that you don’t create a channel with the same name every bit an existing channel.

If you run this program as it is and blazon
!create-channel
into your Discord channel, then y’all’ll see the following error message:

              
              
                $
                python bot.py
                Ignoring exception in command create-channel:
                Traceback (most contempo phone call last):
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/bot.py", line 860, in invoke
                
                  expect ctx.command.invoke(ctx)
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 691, in invoke
                
                  await self.ready(ctx)
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 648, in prepare
                
                  look self._verify_checks(ctx)
                
                  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.vii/site-packages/discord/ext/commands/core.py", line 598, in _verify_checks
                
                  raise CheckFailure('The check functions for command {0.qualified_name} failed.'.format(self))
                discord.ext.commands.errors.CheckFailure: The check functions for command create-aqueduct failed.
              
            

This
CheckFailure
says that
has_role('admin')
failed. Unfortunately, this error only prints to
stdout. It would be better to report this to the user in the channel. To do so, add the following event:

              
              
                @bot
                .
                event
                async
                def
                on_command_error
                (
                ctx
                ,
                error
                ):
                if
                isinstance
                (
                error
                ,
                commands
                .
                errors
                .
                CheckFailure
                ):
                await
                ctx
                .
                ship
                (
                'Yous practice not accept the right office for this command.'
                )
              
            

This event handles an error upshot from the command and sends an informative fault bulletin back to the original
Context
of the invoked
Command.

Endeavor it all over again, and you should come across an error in the Discord channel:

Discord: Role Check Error

Groovy! Now, to resolve the issue, you’ll need to give yourself the
admin
part:

Discord: Grant Admin Role

With the
admin
role, your user will pass the
Check
and volition be able to create channels using the control.

When you type
!create-channel
again, you’ll successfully create the channel
existent-python:

Discord: Navigate to New Channel

Besides, note that you can pass the optional
channel_name
statement to proper name the channel to whatever you want!

With this terminal example, you combined a
Command, an effect, a
Check, and even the
get()
utility to create a useful Discord bot!

Determination

Congratulations! Now, you lot’ve learned how to make a Discord bot in Python. Y’all’re able to build bots for interacting with users in guilds that you create or fifty-fifty bots that other users can invite to interact with their communities. Your bots will be able to reply to messages and commands and numerous other events.

In this tutorial, you learned the basics of creating your own Discord bot. You at present know:

  • What Discord is
  • Why
    discord.py
    is so valuable
  • How to make a Discord bot in the Developer Portal
  • How to create a Discord connection in Python
  • How to handle events
  • How to create a
    Bot
    connection
  • How to employ bot commands, checks, and converters

To read more nearly the powerful
discord.py
library and take your bots to the side by side level, read through their extensive documentation. Too, now that you’re familiar with Discord APIs in general, you have a amend foundation for building other types of Discord applications.

You can too explore the possibilities of ChatterBot, Tweepy, InstaPy, and Alexa Skills to learn more well-nigh how you can make bots for different platforms using Python.


Watch Now

This tutorial has a related video course created by the Real Python squad. Lookout it together with the written tutorial to deepen your understanding:
Creating a Discord Bot in Python

Source: https://realpython.com/how-to-make-a-discord-bot-python/