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

In a globe where video games are then important to and so many people, communication and customs around games are vital. Discord offers both of those and more in one well-designed package. In this tutorial, you'll learn how to make a Discord bot in Python so that y'all can brand the nigh of this fantastic platform.

By the end of this commodity you'll acquire:

  • What Discord is and why it'due south so valuable
  • How to make a Discord bot through the Developer Portal
  • How to create Discord connections
  • How to handle events
  • How to take commands and validate assumptions
  • How to collaborate with various Discord APIs

You'll begin past learning what Discord is and why it'south valuable.

What Is Discord?

Discord is a vocalization and text advice platform for gamers.

Players, streamers, and developers use Discord to discuss games, answer questions, chat while they play, and much more. It even has a game store, complete with disquisitional reviews and a subscription service. Information technology is nigh a ane-stop store for gaming communities.

While in that location are many things you can build using Discord'southward APIs, this tutorial will focus on a particular learning outcome: how to make a Discord bot in Python.

What Is a Bot?

Discord is growing in popularity. As such, automated processes, such as banning inappropriate users and reacting to user requests are vital for a community to thrive and grow.

Automated programs that look and deed like users and automatically respond to events and commands on Discord are called bot users. Discord bot users (or merely bots) accept near unlimited applications.

For case, let'southward say you're managing a new Discord guild and a user joins for the very first time. Excited, you may personally reach out to that user and welcome them to your customs. Y'all might also tell them about your channels or ask them to introduce themselves.

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

Over fourth dimension, your community grows so large that information technology'southward no longer feasible to personally reach out to each new member, but you withal desire to send them something to recognize them as a new member of the guild.

With a bot, it'southward possible to automatically react to the new member joining your club. You can even customize its behavior based on context and control how it interacts with each new user.

This is peachy, but it'southward merely one small instance of how a bot can be useful. There are so many opportunities for yous to be creative with bots, once you know how to make them.

There are ii key steps when y'all're creating a bot:

  1. Create the bot user on Discord and register it with a guild.
  2. Write code that uses Discord's APIs and implements your bot'due south behaviors.

In the next section, you'll learn how to brand a Discord bot in Discord's Developer Portal.

How to Make a Discord Bot in the Programmer Portal

Earlier yous can dive into any Python lawmaking to handle events and create exciting automations, you need to first create a few Discord components:

  1. An account
  2. An application
  3. A bot
  4. A guild

Yous'll larn more about each slice in the post-obit sections.

In one case you've created all of these components, you'll necktie them together past registering your bot with your guild.

You lot can get started by heading to Discord'southward Developer Portal.

Creating a Discord Business relationship

The first affair y'all'll run across is a landing page where yous'll need to either login, if you lot have an existing account, or create a new business relationship:

Discord: Account Login Screen

If y'all need to create a new account, then click on the Annals button below Login and enter your account information.

Once you lot're finished, you'll be redirected to the Developer Portal habitation page, where y'all'll create your application.

Creating an Application

An application allows you to interact with Discord's APIs by providing hallmark tokens, designating permissions, and so 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 fabricated a Discord application. On the resulting screen, you tin see data nearly your application:

Discord: Application General Information

Keep in mind that any plan that interacts with Discord APIs requires a Discord application, non just bots. Bot-related APIs are only a subset of Discord'southward total interface.

Nevertheless, since this tutorial is nigh how to make a Discord bot, navigate to the Bot tab on the left-hand navigation list.

Creating a Bot

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

For your code to really be manifested on Discord, you'll need to create a bot user. To practise so, select Add Bot:

Discord: Add Bot

Once you lot confirm that you want to add together the bot to your awarding, you'll see the new bot user in the portal:

Discord: Bot Created Successfully

Notice that, by default, your bot user will inherit the name of your awarding. Instead, update the username to something more bot-like, such as RealPythonTutorialBot, and Relieve Changes:

Discord: Rename Bot

Now, the bot'due south all gear up and set to go, but to where?

A bot user is not useful if it's not interacting with other users. Adjacent, y'all'll create a guild so that your bot tin can interact with other users.

Creating a Guild

A guild (or a server, as it is often called in Discord's user interface) is a specific grouping of channels where users congregate to conversation.

For example, say you desire to create a infinite where users tin come together and talk nigh your latest game. You lot'd start by creating a guild. Then, in your guild, you could accept multiple channels, such as:

  • General Discussion: A channel for users to talk virtually any they desire
  • Spoilers, Beware: A aqueduct for users who accept finished your game to talk about all the terminate game reveals
  • Announcements: A aqueduct for you lot to announce game updates and for users to talk over them

One time you've created your lodge, you lot'd invite other users to populate it.

So, to create a guild, head to your Discord dwelling folio:

Discord: User Account Home Page

From this dwelling house page, you can view and add friends, direct messages, and guilds. From here, select the + icon on the left-paw side of the 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 proper noun for your guild:

Discord: Naming a Server

In one case yous've finished creating your guild, you'll exist able to see the users on the right-hand side and the channels on the left:

Discord: Newly Created Server

The final stride on Discord is to register your bot with your new lodge.

Adding a Bot to a Guild

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

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

Discord: Application OAuth2

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

This tool generates an say-so URL that hits Discord'southward OAuth2 API and authorizes API access using your application's credentials.

In this case, you'll want to grant your application's bot user access to Discord APIs using your application's OAuth2 credentials.

To practice this, roll downwards and select bot from the SCOPES options and Ambassador from BOT PERMISSIONS:

Discord: Application Scopes and Bot Permissions

Now, Discord has generated your application's authorization URL with the selected scope and permissions.

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

Discord: Add Bot to a Server

Click Authorize, and you're washed!

If you go back to your social club, and then yous'll come across that the bot has been added:

Discord: Bot Added to Guild

In summary, you've created:

  • An application that your bot will use to authenticate with Discord's APIs
  • A bot user that y'all'll use to interact with other users and events in your lodge
  • A gild in which your user account and your bot user will be active
  • A Discord business relationship with which you created everything else and that you'll employ to interact with your bot

Now, you know how to make a Discord bot using the Developer 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, you'll exist using discord.py.

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

Begin by installing discord.py with pip:

                                            $                pip install -U discord.py                          

At present that you've installed discord.py, you'll use information technology to create your kickoff connection to Discord!

Creating a Discord Connection

The starting time step in implementing your bot user is to create a connexion to Discord. With discord.py, you do this past creating an instance of Customer:

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

A Client is an object that represents a connection to Discord. A Customer handles events, tracks state, and by and large 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 information technology has finished preparing the data that Discord has sent, such as login state, lodge and aqueduct data, and more.

In other words, on_ready() will be called (and your bulletin will exist printed) once customer is gear up for further action. You'll learn more about event handlers later in this article.

When yous're working with secrets such every bit your Discord token, it'south adept practice to read it into your plan from an environment variable. Using environment variables helps you lot:

  • Avert putting the secrets into source control
  • Apply different variables for evolution and production environments without changing your code

While y'all could consign DISCORD_TOKEN={your-bot-token}, an easier solution is to save a .env file on all machines that volition be running this lawmaking. This is not simply easier, since you lot won't have to export your token every fourth dimension you articulate your trounce, but it also protects y'all from storing your secrets in your beat out'due south history.

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

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

Yous'll demand to supersede {your-bot-token} with your bot'south token, which you tin can get by going back to the Bot folio on the Programmer Portal and clicking Copy under the TOKEN section:

Discord: Copy Bot Token

Looking dorsum at the bot.py code, y'all'll notice a library called dotenv. This library is handy for working with .env files. load_dotenv() loads surround variables from a .env file into your shell'due south environment variables so that you can use them in your code.

Install dotenv with pip:

                                            $                pip install -U python-dotenv                          

Finally, customer.run() runs your Client using your bot's token.

Now that you've set upwardly both bot.py and .env, you can run your code:

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

Not bad! Your Customer has connected to Discord using your bot'south token. In the next department, you'll build on this Customer past interacting with more than Discord APIs.

Interacting With Discord APIs

Using a Client, yous have access to a wide range of Discord APIs.

For example, let's say you lot wanted to write the proper noun and identifier of the guild that you registered your bot user with to the console.

First, yous'll demand to add a new environment variable:

                            # .env DISCORD_TOKEN={your-bot-token}                DISCORD_GUILD={your-guild-proper name}                                          

Don't forget that you'll need to supplant the ii placeholders with bodily values:

  1. {your-bot-token}
  2. {your-social club-name}

Recollect that Discord calls on_ready(), which you used before, once the Client has made the connection and prepared the data. And so, you can rely on the lodge information being available inside on_ready():

                                            # 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                .                Client                ()                @client                .                upshot                async                def                on_ready                ():                for                guild                in                client                .                guilds                :                if                guild                .                name                ==                Social club                :                break                print                (                f                '                {                client                .                user                }                                  is connected to the following club:                \due north                '                f                '                {                guild                .                name                }                (id:                                {                guild                .                id                }                )'                )                customer                .                run                (                TOKEN                )                          

Here, you looped through the guild data that Discord has sent client, namely client.guilds. Then, you lot found the guild with the matching name and printed a formatted string to stdout.

Run the programme to see the results:

                                            $                python bot.py                RealPythonTutorialBot#9643 is continued to the post-obit guild:                RealPythonTutorialServer(id: 571759877328732195)                          

Great! You can see the name of your bot, the name of your server, and the server'due south identification number.

Some other interesting fleck of data you tin can pull from a guild is the listing of users who are members of the guild:

                                            # 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                .                Client                ()                @customer                .                event                async                def                on_ready                ():                for                guild                in                customer                .                guilds                :                if                guild                .                name                ==                Social club                :                break                print                (                f                '                {                customer                .                user                }                                  is connected to the post-obit guild:                \n                '                f                '                {                guild                .                name                }                (id:                                {                lodge                .                id                }                )                \north                '                )                members                =                '                \n                                  - '                .                join                ([                member                .                name                for                member                in                guild                .                members                ])                print                (                f                'Order Members:                \n                                  -                                {                members                }                '                )                client                .                run                (                TOKEN                )                          

By looping through social club.members, you pulled the names of all of the members of the guild and printed them with a formatted string.

When yous run the program, you should run across at least the name of the account you created the guild with and the name of the bot user itself:

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

These examples barely scratch the surface of the APIs available on Discord, be sure to check out their documentation to meet all that they accept to offer.

Adjacent, yous'll learn about some utility functions and how they tin simplify these examples.

Using Utility Functions

Let's have some other await at the case from the last section where you printed the name and identifier of the bot's guild:

                                            # 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                .                Client                ()                @client                .                event                async                def                on_ready                ():                for                lodge                in                client                .                guilds                :                if                guild                .                name                ==                GUILD                :                break                print                (                f                '                {                client                .                user                }                                  is connected to the post-obit guild:                \due north                '                f                '                {                guild                .                name                }                (id:                                {                guild                .                id                }                )'                )                client                .                run                (                TOKEN                )                          

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

discord.utils.find() is ane utility that can ameliorate the simplicity and readability of this lawmaking by replacing the for loop with an intuitive, abstracted function:

                                            # 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                .                event                async                def                on_ready                ():                guild                =                discord                .                utils                .                observe                (                lambda                1000                :                g                .                proper noun                ==                Social club                ,                customer                .                guilds                )                print                (                f                '                {                client                .                user                }                                  is continued to the following guild:                \n                '                f                '                {                social club                .                proper noun                }                (id:                                {                lodge                .                id                }                )'                )                client                .                run                (                TOKEN                )                          

find() takes a office, chosen a predicate, which identifies some feature of the element in the iterable that you're looking for. Here, you used a item type of anonymous function, called a lambda, every bit the predicate.

In this case, you're trying to notice the social club with the aforementioned name as the i y'all stored in the DISCORD_GUILD environs variable. Once find() locates an element in the iterable that satisfies the predicate, information technology will render the chemical element. This is substantially equivalent to the break argument in the previous example, just cleaner.

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

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

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

In this example, you've identified proper noun=GUILD every bit the aspect that must be satisfied.

At present that yous've learned the basics of interacting with APIs, you'll dive a little deeper into the function that y'all've been using to admission them: on_ready().

Responding to Events

Y'all already learned that on_ready() is an event. In fact, you might have noticed that information technology is identified as such in the code by the client.consequence decorator.

But what is an effect?

An event is something that happens on Discord that you can utilize to trigger a reaction in your lawmaking. Your code will listen for and then reply to events.

Using the case you've seen already, the on_ready() event handler handles the issue that the Client has made a connection to Discord and prepared its response data.

So, when Discord fires an issue, discord.py will route the outcome data to the corresponding event handler on your connected Customer.

There are two ways in discord.py to implement an outcome handler:

  1. Using the customer.issue decorator
  2. Creating a subclass of Client and overriding its handler methods

Yous already saw the implementation using the decorator. Adjacent, take a look at how to subclass Client:

                                            # 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 continued to Discord!'                )                client                =                CustomClient                ()                client                .                run                (                TOKEN                )                          

Here, just similar before, you've created a client variable and called .run() with your Discord token. The actual Client is different, withal. Instead of using the normal base course, customer is an instance of CustomClient, which has an overridden on_ready() role.

There is no difference between the two implementation styles of events, but this tutorial volition primarily utilise the decorator version considering information technology looks similar to how you implement Bot commands, which is a topic yous'll encompass in a flake.

Now that you've learned how to create an issue handler, let'due south walk through some different examples of handlers you can create.

Welcoming New Members

Previously, you saw the example of responding to the event where a member joins a guild. In that instance, your bot user could send them a message, welcoming them to your Discord customs.

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

                                                  # bot.py                  import                  bone                  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                  '                  {                  client                  .                  user                  .                  proper name                  }                                      has connected to Discord!'                  )                  @client                  .                  result                  async                  def                  on_member_join                  (                  member                  ):                  expect                  member                  .                  create_dm                  ()                  await                  fellow member                  .                  dm_channel                  .                  ship                  (                  f                  'Hi                                    {                  member                  .                  name                  }                  , welcome to my Discord server!'                  )                  client                  .                  run                  (                  TOKEN                  )                              

Like before, you handled the on_ready() outcome by printing the bot user'due south name in a formatted string. New, however, is the implementation of the on_member_join() consequence handler.

on_member_join(), as its proper noun suggests, handles the event of a new fellow member joining a social club.

In this instance, you used member.create_dm() to create a straight bulletin channel. And so, yous used that channel to .send() a direct message to that new member.

Now, allow'due south test out your bot's new behavior.

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

                                                  $                  python bot.py                  RealPythonTutorialBot has connected to Discord!                              

At present, head over to Discord, log in, and navigate to your society past selecting it from the left-mitt side of the screen:

Discord: Navigate to Server

Select Invite People just beside the club list where you selected your guild. Check the box that says Set up this link to never expire and copy the link:

Discord: Copy Invite Link

Now, with the invite link copied, create a new account and bring together the order using your invite link:

Discord: Accept Invite

First, you'll run into that Discord introduced yous to the guild by default with an automated message. More importantly though, notice the bluecoat on the left-manus side of the screen that notifies you of a new message:

Discord: Direct Message Notification

When yous select it, you'll see a private message from your bot user:

Discord: Direct Message

Perfect! Your bot user is now interacting with other users with minimal lawmaking.

Next, y'all'll learn how to respond to specific user letters in the conversation.

Responding to Messages

Permit's add on to the previous functionality of your bot past treatment the on_message() event.

on_message() occurs when a message is posted in a aqueduct that your bot has access to. In this example, you'll reply to the message '99!' with a ane-liner from the television set show Brooklyn Nine-Nine:

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

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

The other piece is an of import 1:

                                                  if                  message                  .                  author                  ==                  client                  .                  user                  :                  render                              

Because a Customer tin't tell the difference betwixt a bot user and a normal user account, your on_message() handler should protect confronting a potentially recursive case where the bot sends a message that information technology might, itself, handle.

To illustrate, let'south say y'all want your bot to listen for users telling each other 'Happy Altogether'. You lot could implement your on_message() handler like this:

                                                  @client                  .                  upshot                  async                  def                  on_message                  (                  message                  ):                  if                  'happy altogether'                  in                  message                  .                  content                  .                  lower                  ():                  look                  message                  .                  channel                  .                  send                  (                  'Happy Birthday! 🎈🎉'                  )                              

Aside from the potentially spammy nature of this event handler, it also has a devastating side effect. The message that the bot responds with contains the same message it's going to handle!

So, if i person in the channel tells some other "Happy Birthday," then the bot will too chime in… once again… and once more… and once again:

Discord: Happy Birthday Message Repetition

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

So, permit's set bot.py:

                                                  # bot.py                  import                  bone                  import                  random                  import                  discord                  from                  dotenv                  import                  load_dotenv                  load_dotenv                  ()                  TOKEN                  =                  bone                  .                  getenv                  (                  'DISCORD_TOKEN'                  )                  customer                  =                  discord                  .                  Client                  ()                  @customer                  .                  event                  async                  def                  on_ready                  ():                  print                  (                  f                  '                  {                  client                  .                  user                  .                  name                  }                                      has continued to Discord!'                  )                  @client                  .                  event                  async                  def                  on_member_join                  (                  member                  ):                  await                  member                  .                  create_dm                  ()                  look                  member                  .                  dm_channel                  .                  transport                  (                  f                  'How-do-you-do                                    {                  fellow member                  .                  name                  }                  , welcome to my Discord server!'                  )                  @customer                  .                  result                  async                  def                  on_message                  (                  message                  ):                  if                  message                  .                  author                  ==                  client                  .                  user                  :                  return                  brooklyn_99_quotes                  =                  [                  'I                  \'                  m the human course of the 💯 emoji.'                  ,                  'Bingpot!'                  ,                  (                  'Cool. Cool cool cool absurd cool cool cool, '                  'no incertitude no doubt no uncertainty no doubt.'                  ),                  ]                  if                  message                  .                  content                  ==                  '99!'                  :                  response                  =                  random                  .                  pick                  (                  brooklyn_99_quotes                  )                  expect                  message                  .                  channel                  .                  ship                  (                  response                  )                  customer                  .                  run                  (                  TOKEN                  )                              

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

Run the program:

                                                  $                  python bot.py                  RealPythonTutorialBot has connected to Discord!                              

Finally, caput over to Discord to test information technology out:

Discord: Quotes From Brooklyn Nine-Nine

Great! Now that you've seen a few different ways to handle some common Discord events, y'all'll larn how to deal with errors that event handlers may enhance.

Handling Exceptions

As you've seen already, discord.py is an event-driven system. This focus on events extends fifty-fifty to exceptions. When one event handler raises an Exception, Discord calls on_error().

The default behavior of on_error() is to write the fault 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                  .                  Customer                  ()                  @client                  .                  event                  async                  def                  on_ready                  ():                  print                  (                  f                  '                  {                  customer                  .                  user                  .                  name                  }                                      has continued to Discord!'                  )                  @customer                  .                  effect                  async                  def                  on_member_join                  (                  fellow member                  ):                  expect                  fellow member                  .                  create_dm                  ()                  await                  fellow member                  .                  dm_channel                  .                  send                  (                  f                  'Hi                                    {                  fellow member                  .                  name                  }                  , welcome to my Discord server!'                  )                  @client                  .                  consequence                  async                  def                  on_message                  (                  message                  ):                  if                  message                  .                  author                  ==                  client                  .                  user                  :                  return                  brooklyn_99_quotes                  =                  [                  'I                  \'                  m the human course of the 💯 emoji.'                  ,                  'Bingpot!'                  ,                  (                  'Cool. Absurd absurd absurd absurd cool absurd cool, '                  'no dubiousness no dubiety no dubiousness no doubt.'                  ),                  ]                  if                  bulletin                  .                  content                  ==                  '99!'                  :                  response                  =                  random                  .                  pick                  (                  brooklyn_99_quotes                  )                  await                  message                  .                  aqueduct                  .                  send                  (                  response                  )                                      elif                    message                    .                    content                    ==                    'enhance-exception'                    :                                                        raise                    discord                    .                    DiscordException                                    client                  .                  run                  (                  TOKEN                  )                              

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

Run the program and type heighten-exception into the Discord channel:

Discord: Raise Exception Message

You should now see the Exception that was raised past your on_message() handler in the console:

                                                  $                  python bot.py                  RealPythonTutorialBot has connected to Discord!                  Ignoring exception in on_message                  Traceback (most recent call final):                                      File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/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 error handler, so the output contains the message Ignoring exception in on_message. Let's fix that past handling that particular mistake. To exercise so, you'll catch the DiscordException and write information technology to a file instead.

The on_error() consequence handler takes the event as the first argument. In this case, nosotros expect the event to be 'on_message'. It also accepts *args and **kwargs as flexible, positional and keyword arguments passed to the original upshot handler.

Then, since on_message() takes a single argument, message, we expect args[0] to be the message that the user sent in the Discord channel:

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

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

Run bot.py and send the heighten-exception bulletin once more to view the output in err.log:

                                                  $                  cat err.log                  Unhandled bulletin: <Message id=573845548923224084 pinned=Simulated writer=<Member id=543612676807327754 proper name='alexronquillo' discriminator='0933' bot=False nick=None guild=<Guild id=571759877328732195 name='RealPythonTutorialServer' chunked=True>>>                              

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

Now that y'all have some experience treatment different events and interacting with Discord APIs, you'll learn most a subclass of Customer called Bot, which implements some handy, bot-specific functionality.

Connecting a Bot

A Bot is a subclass of Client that adds a petty fleck of extra functionality that is useful when y'all're creating bot users. For example, a Bot can handle events and commands, invoke validation checks, and more.

Before you lot become into the features specific to Bot, convert bot.py to use a Bot instead of a Customer:

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

As you can encounter, Bot tin can handle events the aforementioned way that Customer does. However, notice the differences betwixt Client and Bot:

  1. Bot is imported from the discord.ext.commands module.
  2. The Bot initializer requires a command_prefix, which you lot'll acquire more well-nigh in the next section.

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

Using Bot Commands

In general terms, a control is an social club that a user gives to a bot so that it will exercise something. Commands are different from events because they are:

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

In technical terms, a Command is an object that wraps a part that is invoked by a text command in Discord. The text command must start with the command_prefix, defined by the Bot object.

Let's take a look at an old event to better understand what this looks like:

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

Hither, you lot created an on_message() issue handler, which receives the message string and compares it to a pre-defined choice: '99!'.

Using a Command, yous can convert this case to be more specific:

                                                  # bot.py                  import                  bone                  import                  random                  from                  discord.ext                  import                  commands                  from                  dotenv                  import                  load_dotenv                  load_dotenv                  ()                  TOKEN                  =                  bone                  .                  getenv                  (                  'DISCORD_TOKEN'                  )                  bot                  =                  commands                  .                  Bot                  (                  command_prefix                  =                  '!'                  )                  @bot                  .                  control                  (                  name                  =                  '99'                  )                  async                  def                  nine_nine                  (                  ctx                  ):                  brooklyn_99_quotes                  =                  [                  'I                  \'                  m the homo form of the 💯 emoji.'                  ,                  'Bingpot!'                  ,                  (                  'Cool. Absurd cool cool cool cool cool cool, '                  'no dubiety no doubtfulness no incertitude no doubtfulness.'                  ),                  ]                  response                  =                  random                  .                  selection                  (                  brooklyn_99_quotes                  )                  await                  ctx                  .                  send                  (                  response                  )                  bot                  .                  run                  (                  TOKEN                  )                              

At that place are several important characteristics to sympathise about using Command:

  1. Instead of using bot.issue similar earlier, you use bot.command(), passing the invocation control (proper name) every bit its argument.

  2. The function will now simply be called when !99 is mentioned in chat. This is dissimilar than the on_message() consequence, which was executed any 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 y'all defined in the initializer for your Bot.

  4. Whatever Command function (technically called a callback) must accept at least ane parameter, called ctx, which is the Context surrounding the invoked Command.

A Context holds information such as the channel and guild that the user called the Control from.

Run the plan:

With your bot running, you lot can now head to Discord to try out your new command:

Discord: Brooklyn Nine-Nine Command

From the user's point of view, the applied deviation is that the prefix helps formalize the control, rather than simply reacting to a particular on_message() consequence.

This comes with other swell benefits besides. For instance, you can invoke the !assistance command to see all the commands that your Bot handles:

Discord: Help Command

If y'all desire to add together a description to your command so that the help message is more informative, simply laissez passer a help description 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                  .                  command                  (                  name                  =                  '99'                  ,                  help                  =                  'Responds with a random quote from Brooklyn 99'                  )                  async                  def                  nine_nine                  (                  ctx                  ):                  brooklyn_99_quotes                  =                  [                  'I                  \'                  m the human form of the 💯 emoji.'                  ,                  'Bingpot!'                  ,                  (                  'Cool. Cool cool cool cool cool absurd cool, '                  'no dubiousness no doubt no doubt no uncertainty.'                  ),                  ]                  response                  =                  random                  .                  choice                  (                  brooklyn_99_quotes                  )                  await                  ctx                  .                  send                  (                  response                  )                  bot                  .                  run                  (                  TOKEN                  )                              

Now, when the user invokes the !help command, your bot will nowadays a description of your command:

Discord: Informative Help Description

Continue in mind that all of this functionality exists but for the Bot subclass, non the Customer superclass.

Command has another useful functionality: the ability to utilise 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 sure type, but arguments to a Command function are, past default, strings. A Converter lets you convert those parameters to the type that you look.

For example, if you lot want to build a Command for your bot user to simulate rolling some dice (knowing what you've learned so far), you might define information technology similar this:

                                                  @bot                  .                  command                  (                  proper name                  =                  'roll_dice'                  ,                  help                  =                  'Simulates rolling dice.'                  )                  async                  def                  roll                  (                  ctx                  ,                  number_of_dice                  ,                  number_of_sides                  ):                  dice                  =                  [                  str                  (                  random                  .                  option                  (                  range                  (                  1                  ,                  number_of_sides                  +                  i                  )))                  for                  _                  in                  range                  (                  number_of_dice                  )                  ]                  await                  ctx                  .                  send                  (                  ', '                  .                  bring together                  (                  dice                  ))                              

You divers roll to take 2 parameters:

  1. The number of dice to coil
  2. The number of sides per dice

Then, you lot busy it with .control() so that y'all tin invoke information technology with the !roll_dice control. Finally, you lot .ship() the results in a message back to the channel.

While this looks correct, information technology isn't. Unfortunately, if you lot run bot.py, and invoke the !roll_dice command in your Discord channel, you'll see the following error:

                                                  $                  python bot.py                  Ignoring exception in control roll_dice:                  Traceback (near recent phone call final):                                      File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.vii/site-packages/discord/ext/commands/cadre.py", line 63, in wrapped                                      ret = await coro(*args, **kwargs)                                      File "bot.py", line forty, in roll                                      for _ in range(number_of_dice)                  TypeError: 'str' object cannot be interpreted as an integer                  The above exception was the direct cause of the following exception:                  Traceback (nigh recent call last):                                      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                                      raise CommandInvokeError(exc) from exc                  discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: 'str' object cannot be interpreted equally an integer                              

In other words, range() tin can't take a str as an statement. Instead, it must exist an int. While you could cast each value to an int, at that place is a better way: you can use a Converter .

In discord.py, a Converter is defined using Python 3's part annotations:

                                                  @bot                  .                  command                  (                  name                  =                  'roll_dice'                  ,                  help                  =                  'Simulates rolling dice.'                  )                  async                  def                  curlicue                  (                  ctx                  ,                  number_of_dice                  :                  int                  ,                  number_of_sides                  :                  int                  ):                  die                  =                  [                  str                  (                  random                  .                  choice                  (                  range                  (                  one                  ,                  number_of_sides                  +                  i                  )))                  for                  _                  in                  range                  (                  number_of_dice                  )                  ]                  await                  ctx                  .                  transport                  (                  ', '                  .                  join                  (                  dice                  ))                              

Y'all added : int annotations to the two parameters that yous wait to be of type int. Try the command once again:

Discord: Bot Dice-Rolling Command

With that petty change, your command works! The difference is that you lot're now converting the control arguments to int, which makes them compatible with your function's logic.

Side by side, you'll learn about the Check object and how it can better your commands.

Checking Command Predicates

A Check is a predicate that is evaluated before a Command is executed to ensure that the Context surrounding the Command invocation is valid.

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

                                                  if                  message                  .                  author                  ==                  client                  .                  user                  :                  return                              

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

To demonstrate how this works, assume yous want to back up a control !create-channel <channel_name> that creates a new aqueduct. Nevertheless, y'all but want to let administrators the ability to create new channels with this command.

First, you'll need to create a new member role in the admin. Go into the Discord guild and select the {Server Proper noun} → Server Settings menu:

Discord: Server Settings Screen

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

Discord: Navigate to Roles

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

Discord: Create New Admin Role

Now, you've created an admin part that y'all can assign to particular users. Next, y'all'll update bot.py to Cheque the user's role before allowing them to initiate the control:

                                                  # bot.py                  import                  os                  import                  discord                  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                  =                  'create-channel'                  )                  @commands                  .                  has_role                  (                  'admin'                  )                  async                  def                  create_channel                  (                  ctx                  ,                  channel_name                  =                  'real-python'                  ):                  society                  =                  ctx                  .                  society                  existing_channel                  =                  discord                  .                  utils                  .                  get                  (                  guild                  .                  channels                  ,                  proper noun                  =                  channel_name                  )                  if                  not                  existing_channel                  :                  print                  (                  f                  'Creating a new channel:                                    {                  channel_name                  }                  '                  )                  await                  social club                  .                  create_text_channel                  (                  channel_name                  )                  bot                  .                  run                  (                  TOKEN                  )                              

In bot.py, you have a new Command function, called create_channel() which takes an optional channel_name and creates that channel. create_channel() is also decorated with a Bank check called has_role().

You lot as well utilize discord.utils.get() to ensure that you don't create a channel with the same name as an existing channel.

If you run this program as information technology is and type !create-channel into your Discord channel, then you'll run across the following error bulletin:

                                                  $                  python bot.py                  Ignoring exception in control create-aqueduct:                  Traceback (virtually recent call terminal):                                      File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/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 691, in invoke                                      await cocky.prepare(ctx)                                      File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 648, in set up                                      look self._verify_checks(ctx)                                      File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/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 control create-channel failed.                              

This CheckFailure says that has_role('admin') failed. Unfortunately, this fault only prints to stdout. Information technology would exist improve to written report this to the user in the channel. To do so, add together the following effect:

                                                  @bot                  .                  event                  async                  def                  on_command_error                  (                  ctx                  ,                  error                  ):                  if                  isinstance                  (                  error                  ,                  commands                  .                  errors                  .                  CheckFailure                  ):                  await                  ctx                  .                  send                  (                  'You do not have the right part for this command.'                  )                              

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

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

Discord: Role Check Error

Great! Now, to resolve the issue, you lot'll need to give yourself the admin role:

Discord: Grant Admin Role

With the admin function, your user will pass the Bank check and will be able to create channels using the control.

When yous type !create-aqueduct again, you'll successfully create the channel existent-python:

Discord: Navigate to New Channel

Also, notation that you can laissez passer the optional channel_name argument to name the channel to whatever y'all want!

With this last case, you combined a Command, an event, a Bank check, and even the get() utility to create a useful Discord bot!

Conclusion

Congratulations! Now, y'all've learned how to make a Discord bot in Python. You're able to build bots for interacting with users in guilds that you create or even bots that other users can invite to collaborate with their communities. Your bots volition exist able to respond to messages and commands and numerous other events.

In this tutorial, you learned the basics of creating your ain Discord bot. Y'all 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 use bot commands, checks, and converters

To read more than nigh the powerful discord.py library and accept your bots to the next level, read through their extensive documentation. Also, now that you're familiar with Discord APIs in general, y'all have a better foundation for building other types of Discord applications.

Watch Now This tutorial has a related video form created by the Real Python team. Watch it together with the written tutorial to deepen your agreement: Creating a Discord Bot in Python