Developing a Discord Bot for Language Practice
Discord is a popular chat platform that helps communities interact. You can find large communities around interests ranging from gaming to programming. But you can also find smaller communities of close friends who use the platform to keep in touch.
In addition to its chat platform, Discord also makes it relatively easy to create bots that can help enliven the community. Some Discord bots perform actions that simplify the management of large communities, such as removing spam or providing key information. Other bots might provide enjoyable features, like playing songs or telling jokes.
In this article, we’ll create a very limited Discord bot to practice vocabulary. When prompted, our bot will send us a word. We will respond with the right translation of that word. We can choose whether the vocabulary word is in the target language or our native language. The bot will connect to an Airtable to retrieve vocabulary words of interest to us. Our goal is to develop a very limited prototype to see how to connect these different systems and how to work with Discord.
To start with, we need to create a bot application in Discord. Go to the Discord Developer Portal. Click New Application.
In the popup, provide a name for your bot, agree to the terms, and click Create.
With this, we have registered a bot with Discord. However, we’ll still need to code the bot’s logic and capabilities ourselves. We need 3 pieces of information from our bot application page, though. We need the application id, bot token, and bot secret. Click the OAuth2 tab.
On the OAuth2 page, you can find the application id under CLIENT ID and the secret under CLIENT SECRET. For the secret, you will need to click Reset Secret. Copy both those values to an .env file.
DISCORD_BOT_ID= # Your bot id
DISCORD_BOT_SECRET= # Your bot secret
DISCORD_BOT_TOKEN=
Now we need an authentication token. Click on the Bot tab.
You can find the authentication token under TOKEN. As before, you may have to click Reset Token. Copy the token value to your .env file.
DISCORD_BOT_ID= # Your bot id
DISCORD_BOT_SECRET= # Your bot secret
DISCORD_BOT_TOKEN= # Your bot token
While in the bot tab, we may want to adjust some settings. You can make the bot a PUBLIC BOT so that anyone can add the bot to their servers. Also, in order to receive full message content, you may want to set the MESSAGE CONTENT INTENT.
With our bot set up, we now need to add it to our server. Again, click the OAuth2 tab. Then click URL Generator.
In the SCOPES field, select bot.
In the BOT PERMISSIONS field, select all of the TEXT PERMISSIONS options.
Now, click Copy to receive the join link. You can provide this link to others to let them add your bot to their own servers.
Paste the link into your browser. Select the server you want your bot to join. For this tutorial, I just selected my personal server. Your own free, personal server is a great place to test out the bot. However, do note that it has some restrictions if you want to make a more complex bot. For example, a free server doesn’t have access to the forum channel feature.
So far, we’ve registered a bot application with Discord, adjusted its settings, and added it to a server. However, our bot can’t really do anything right now. We haven’t programmed any capabilities. We’ll program our bot using Python and connect our Python script to Discord via the credentials we just copied to our .env file.
Let’s set up our environment. There are two main libraries that support developing Discord bots in Python - Discord.py and Pycord. Pycord is actually a fork of Discord.py. However, both seem to be in active development. For this tutorial, we will use Discord.py.
pip install discord.py
Create a python file named lingobot.py in the same directory as your .env file. In lingobot.py, we’ll start with our imports and connect our script to a variable.
# Import packages
import os
import discord
# Log .env variables into the script
from dotenv import load_dotenv
load_dotenv()
# Assign .env variable values to variable names
DISCORD_BOT_ID = os.environ.get('DISCORD_BOT_ID')
DISCORD_BOT_TOKEN = os.environ.get('DISCORD_BOT_TOKEN')
DISCORD_BOT_SECRET = os.environ.get('DISCORD_BOT_SECRET')
Next we’ll instantiate our Discord bot. Put the client and token under the .env variables. client.run(token)
will go at the end of our file. You can run the bot now by entering python lingobot.py
in your terminal. The bot should pop up in the Member list of your server. However, it won’t do much else.
client = discord.Client(intents=discord.Intents.all())
token = DISCORD_AUTHENTICATION_TOKEN
# Where the logic of our bot goes
client.run(token)
Let’s have the bot announce that it is active. Discord.py revolves around events that the bot should listen for and act upon. There are a lot of events that you can use. Use the @client.event
decorator to indicate that a function is one of these events. For our first event, we will just have the bot announce that it is logged in and ready. Add the client event to the lingobot.py file as shown below.
client = discord.Client(intents=discord.Intents.all())
token = DISCORD_AUTHENTICATION_TOKEN
@client.event
async def on_ready():
print(f'Bot logged in as {client.user}')
client.run(token)
Now if you run the bot, you will notice that no message appears in our Discord server. Where did the message go? We just printed it to the terminal. Let’s add another event that helps us send actual messages.
Under the on_ready() event code, we’ll add a new event. on_message
listens to the server for messages. When a message is received, we can program our Discord bot to do certain things.
First we’ll add the event. We’ll also capture the message the user sent to user_message
.
@client.event
async def on_message(message):
user_message = str(message.content)
Now the bot will listen to each message sent. However, that also means the bot will listen to its own messages. That could create a situation where the bot responds to itself nonstop. We’ll add a condition to filter out messages by the bot (ie., client.user
)
@client.event
async def on_message(message):
user_message = str(message.content)
if message.author == client.user:
return
To listen for a specific message in a specific channel, we need to add another condition to on_message
.
@client.event
async def on_message(message):
user_message = str(message.content)
if message.author == client.user:
return
if message.channel.name =='general':
if user_message.lower() == 'hello':
await message.channel.send('Hello')
If a user types “hello” into the general channel, the bot will respond “Hello.”
The on_message
function we’ve built so far shows how much data we can work with from even a single message. So far, the message
object contains content, author, and channel. The channel is actually its own object with its own attributes and methods. You can view more about what’s available in Discord.py’s Message model documentation.
With our basic Discord bot set up, we can now work on the initial language learning practice feature. To do that, though, we need to create an Airtable that will act as our database of vocabulary. If you haven’t already, you’ll need to visit https://airtable.com to make a free Airtable account and create a new base, what Airtable calls a spreadsheet. The base should have 2 language-related columns - the language you are studying and your native language. In my case, Column 1 is Korean, and Column 2 is English. It should also have a Pasted field column and Score column. The Pasted field is just the date you entered the vocab word. The score is what the bot will use to tell how many times you’ve gotten the question right. Here’s an example.
Now, we’ll need to set up an API integration. We’ll need three things from Airtable to connect our base with the Discord bot. The first tab of my base is named “Vocab.” You can use whatever name you want, but you will need to include that name in your .env file.
AIRTABLE_API_KEY=
AIRTABLE_BASE_ID=
AIRTABLE_TABLE_NAME=Vocab # Make sure to use your base's name
Next, we’ll need to get the base id. You can find this in the URL for the base. Inside the URL, there is a parameter that starts with “app”. For example, consider the following fake Airtable URL below:
https://airtable.com/appFSD3547SFssh/fsYHW473fhss/34fsDH34hsfSFH?blocks=hide
“appFSD3547SFssh” starts with “app” and represents this base’s id. Copy that entire string, including “app”, to your .env file as the AIRTABLE_BASE_ID.
Finally, go to the profile icon in the upper right. Click Account. Under the API section, click on Generate API Key.
Copy this API key to the AIRTABLE_API_KEY.
AIRTABLE_API_KEY= # You api key
AIRTABLE_BASE_ID= # Your base id
AIRTABLE_TABLE_NAME=Vocab # Make sure to use your base's name
We’re all set to develop our vocab practice activity now! Let’s get back to Discord.
Add the Airtable .env variables under the Discord .env variables.
AIRTABLE_API_KEY = os.environ.get('AIRTABLE_API_KEY')
AIRTABLE_BASE_ID = os.environ.get('AIRTABLE_BASE_ID')
AIRTABLE_TABLE_NAME = os.environ.get('AIRTABLE_TABLE_NAME')
To use them, we need to install another Python package - PyAirtable.
pip install pyairtable
With that set up, we can use the Airtable .env variables to get our vocabulary table into the Discord bot.
vocab_table = Table(AIRTABLE_API_KEY, AIRTABLE_BASE_ID, AIRTABLE_TABLE_NAME)
Above the on_ready
event, make a dictionary that will hold the current state of the bot. Since we are practicing vocabulary, ‘target_word’ will be the word the Discord bot presents to us. ‘answer’ is the translation of that word. ‘current_activity’ will be either ‘translate’ or ‘translate_reverse’, basically which language the target word is in.
current_state = {
'target_word': '',
'current_activity': '',
'answer': ''
}
@client.event
async def on_ready():
print(f'Bot logged in as {client.user}')
We’ll add to the “general channel” condition under on_message
. Note that you could make a channel specifically for language learning practice. In that case, you would want to make a new condition specific to that channel. Here, we are adding global current_state
. This will allow us to update that variable with each interaction.
if message.channel.name =='general':
global current_state
Now we’ll add another condition under the “hello” condition. When a user types “vocab”, the Discord bot will present a vocabulary word in the target language.
global current_state
if user_message.lower() == 'vocab':
voc = vocab_table.all()[0]
await message.channel.send(voc['fields']['Korean'])
current_state = {
'target_word': voc['fields']['Korean'],
'current_activity': 'translate',
'answer': voc['fields']['English']
}
This condition will respond to the trigger phrase “vocab.”
Even if we answer, though, the bot won’t do anything. We’ll get to that in a second. Before that, we’ll do a similar practice activity, but reversed. Instead of the word appearing in the language we are studying, it will appear in our native language.
if user_message.lower() == 'reverse':
voc = vocab_table.all()[0]
await message.channel.send(voc['fields']['English'])
current_state = {
'target_word': voc['fields']['English'],
'current_activity': 'translate_reverse',
'answer': voc['fields']['Korean']
}
When we type “reverse”, the Discord bot will respond with our native language, in my case English.
Now, we need the Discord bot to evaluate our answer. So let’s create an answer condition. Under the general channel condition, we’ll add a new condition to deal with the answer. The user’s response should start with “answer”, and then they will write the translated word. In my case, “answer 공개하다” or “answer publish” depending on the activity.
if user_message.startswith('answer'):
user_answer = user_message.split()[1]
target_answer = current_state['answer']
if user_answer == current_state['answer']:
await message.channel.send("You got it! +1 for that word!")
formula = match({'English': current_state['answer']})
resp = vocab_table.first(formula=formula)
score = resp['fields']['Score'] + 1
vocab_table.update(resp['id'], {'Score': score})
else:
await message.channel.send("Not quite, let's practice more.")
Now, if you run the bot, you should be able to do some practice activities. Below, I should have entered “answer publish” to get the question right.
This walkthrough has given an introduction to creating a Discord bot. It’s shown you how to set up a bot, program it, and use some of its events. By programming this limited prototype, you have also gotten some familiarity with how to work with messages and even connect to a database of sorts.
That said, there’s still a lot we could do to make this a more effective learning tool. Just look at the way the bot selects a vocab word. It is hardcoded to use the first entry in the Airtable base. This means the practice word will always be the same. How might we change this so the Discord bot provides a random word each time? Also, the Score column is updated when an answer is right. Instead of presenting the user with a random word, could we provide them a word that has a low score? Could we add other fields to make the word selection more complex, such as the date the word was last practiced? Play around with it using the Discord.py documentation and your imagination!
I’ll revisit some of these issues and more in another post.