Learn to Code via Tutorials on Repl.it!

← Back to all posts
Discord bot in Python!
h
InvisibleOne (3227)

How to make a Discord Bot in Python!

Reason(s) for making this tutorial:

1. I was bored
2. I was really bored
3. I wanted to
4. People keep needing my help to make them
5. Python is really cool

Now, on to the tutorial.

The first thing you are going to need to do when you make a discord bot, is have a discord account, but I’m pretty sure you knew that already.
The next thing you need to do is go to Discord Developer Portal
Then click to make a new application

Give you application a name and then click create.

Give your application a description and avatar if you like, then select “bot” on the left side of your screen.

Then click on add a bot, confirm it, then give it a name and avatar. Scroll down to the bottom of the page and give it the access rights you want it to have. I just selected “Administrator” which pretty much gives it access to everything it wants.

Then scroll back to the top of the page and copy the token

You’ll need it when we make the bot.

Next, navigate back to replit but don’t close the discord tab, we’ll go right back to that in a few minutes, and open a new python repl.

The first thing you’ll want to do is open up the secret’s tab and make a new secret to hold your token. I just named mine “DISCORD_TOKEN”.

Now go back to the discord tab and click on Oauth on the left side of your screen.

Next you need to select the scopes that your bot will have in your server. I selected “Bot” and then “Administrator”

Then copy the url it generates and open that in a new tab.

This will give you the option to add this bot to your server. After you done the bot should be added into your server, you can close these tabs and go back to replit.

Inside your repl you’ll need to make some imports, for a very basic bot you’ll need discord , os and maybe random

But since this will be a cool bot we are going to use commands, so we need a few more imports. We need to import commands from discord.ext, time , os and random as well as discord itself.

from discord.ext import commands
import discord, os, random, time

Next we need to get our token from the secret, we can do that like this:

TOKEN = os.environ["DISCORD_TOKEN"]

Then we’ll initiate our bot, you can set the prefix to whatever you like, but for this tutorial I’ll just be using the exclamation mark.

prefix = "!"
bot = commands.Bot(prefix)

Next, we run the bot at the bottom of the code.

bot.run(TOKEN)

And now our bot should be running, but it doesn’t do anything and we can’t really tell if it is running. So first things first let’s set it up to tell us when it runs.
We can do that using the on_ready() function that is built in.
All we need to do is first make a bot event, with @bot.event and then underneath that we put the function we want it to do, which is on_ready()
The function as async so we need to put that keyword before def when making this function, like this:

@bot.event
async def on_read():
	print("Bot is ready!")

Now when we run our bot, we should see that is says it’s ready.

Now let’s make some commands. The easiest way to make a command (in my opinion) is to just use @bot.command()

Underneath that we create an async function, with the name of the function being the command word.

Like this:

@bot.command()
async def ping(ctx):
	await ctx.send("Pong")

Incase you don’t know, ctx (short for context) is basically how we access the message that the bot received that triggered this command. It gives us access to all sorts of stuff about the person sending the message.

We also use ctx to send a message, back, making sure to use await.

Now we should have a mini little discord bot, and your code should look something like this:

from discord.ext import commands
import discord, os, random, time

TOKEN = os.environ['DISCORD_TOKEN']

prefix = "$"

bot = commands.Bot(prefix)


@bot.event
async def on_read():
  print("Bot is ready!")

@bot.command()
async def ping(ctx):
  await ctx.send("Pong")



bot.run(TOKEN)

If you run it and then go to the server you put it in, type the command $ping then should should the the result: pong

Now to add some more complex commands. First, let’s try getting the bot to flip a coin for us.
We’ll add the command:

@bot.command()
async def flipcoin(ctx):
	pass

Then we’ll use random to pick between heads or tails, and then send that result back to the user.

@bot.command()
async def flipcoin(ctx):
	heads_tails = ['Heads', 'Tails']
	
	choice = random.choice(heads_tails)
	
	await ctx.send(choice)

Now let’s try it out and see if it works.

Ok, that wasn’t too hard, now let’s make something even more difficult, how about a random number from zero to say, whatever the user inputs.
The command would look something like this $rand 100 which should result in a random number between zero and one hundred, but we are going to have to get the max number from the user.
Thankfully, discord does this almost automatically, all we have to do is put another argument in the function for the command, and if a second word is included that will be put as the command.
Let’s try it out:

@bot.command()
async def rand(ctx, num=10): # defualts to 10 if nothing is included. 
	random_number = random.randint(0, int(num)) # don't forget to convert from string to a number
	
	await ctx.send(str(random_number))

Check if it works in the channel, and if everything is right you should be able to generate a random number.

And it doesn’t just have to be numbers, we can also return random quotes, dares, questions, pretty much whatever you want.
I mean if you want to you could do this:

@bot.command()
async def banana(ctx):
	await ctx.send("BANANA BANANA BANANA BANANA BANANA")

And you don’t just have to do commands, you can also do things on a message, even if the message isn’t a command.

Instead of using @bot.command() for this, you use a @bot.event with the premade function on_message(message) to see when a message is sent.
We can then do whatever we want with this message.

If for example, we wanted to cheer up people when they are sad, we could make a list of sad words:

sad_words = ['sad', 'unhappy', 'crying']

And then when a user sends a message, we can check to see if any of the words in that message are sad words, and then give them a encouraging message, like this:

@bot.event
async def on_message(message):
	for word in message.split(" "):
		if word in sad_words:
			await message.channel.send("Don't be sad, it's that easy!")
			break

Just note that instead of doing await ctx.send() to send a message, if you are using the on message event you have to do await message.channel.send()
Just keep that in mind.

What we just did above is especially helpful if we want to see if someone swears or something, and then if we do we can give them a warning not to do it again.

Or if we wanted to find more creative uses for this feature, we could do something like this:

rick_words = ['rick', 'roll', 'rickroll']
@bot.event
async def on_message(message):
	for word in message.split(" "):
		if word in rick_words:
			await message.channel.send("https://www.youtube.com/watch?v=dQw4w9WgXcQ")

But you also need to keep in mind that you can only have one on_message() event, so you can’t do this:

@bot.event
async def on_message(message):
	if message == 'bob':
		await message.channel.send("bob")
@bot.event
async def on_message(message):
	if message == 'bill':
		await message.channel.send("bill")

Instead we just have to stack up a whole bunch of if statements, like this:

@bot.event
async def on_message(message):
	if message == 'bob':
		await message.channel.send("bob")
	elif message == 'bill':
		await message.channel.send("bill")

Which is why some people prefer @bot.command() over @bot.event because you can use lots of commands but only one on_message() so you have to stack up all your code under that one function which can end up being really messy and confusing.

A few more things

I kind of got off track, but there’s still a few more things you need to know. The first is how to use embeds, which make stuff look cooler in discord messages.
To send an embed, we first create it with discord.Embed() and I know that there are at least two arguments that you have to have, with the third color being optional.

The two that you have to have are:

title which is the title of your embed, and description

After we’ve created an embed, we can then add fields to it, footers, images and more. But for this tutorial I’m just going to focus on creating the embed and adding fields and a single image.

We need to a help command for our bot so people who don’t know what commands there are can find them, so we do this:

@bot.command()
async def help(ctx):
	embed = discord.Embed(title="Commands", description="All commands must start with the prefix `$`")
	
	# add fields

	embed.add_field(title="Flip a coin", value="`$flipcoin`", inline=False)
	embed.add_field(title="Random Number", value="`$rand <num>` with `<num>` being optional")

	# then send the embed

	ctx.send(embed=embed)

Now there’s going to be a bit of a problem, if you run your bot and then try and use the $help command, your probably going to get some sort of error like this:

Traceback (most recent call last):
  File "main.py", line 37, in <module>
    async def help(ctx):
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 1263, in decorator
    self.add_command(result)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 1149, in add_command
    raise CommandRegistrationError(command.name)
discord.ext.commands.errors.CommandRegistrationError: The command help is already an existing command or alias.

The issue is that by default there is already a help command, but since we don’t want to use that help command, we’ll need to remove it at the top of the code right after the imports and then run our help command (which is much better)

We can do that with this little line of code:

bot.remove_command('help')

Just make sure to put it right after the line:

bot = commands.Bot(prefix)

And now to make something a bit more complex, a currency system.

CURRENCY SYSTEM

A lot of discord bots have currency systems, so I thought I would explain how to make a simple one.
This currency system be very basic, it will have:

* A command for paying other users money
* The ability to “claim money”
* A cool down on the command
* More money for daily streaks.
* A simple leaderboard of who has the most money

Now, I’ll be using MongoDB for my database. As I don’t want to write a complete tutorial on it now, as I already did, here’s the link for the tutorial I wrote a while ago: MongoDB How to tell how many burritos 🌯 or tacos 🌮 are in a database with mongoDB - Replit

As well as a cool one that TheDrone7 made: Using MongoDB with python (using PyMongo) - Replit

Now for the python code.

I’m putting all the save stuff in a different python file to make things tidy, but you don’t have to do that unless you want to.

We’ll start by connecting to the mongo db:

import pymongo
import dns
import os

client = pymongo.MongoClient(os.environ['MONGO_TOKEN']) # save your url in a .env to keep it safe

database = client['database']['tutorial_stuff']

Then make a few functions to save, restore, check if a user exists in the database and create new users:

def createUser(_id):
  basicData = {
    'userid' : _id,
    'smackers' : 0,
    'lastTime' : 0,
    'bonusTime' : 0,
  }
  try:
    t = database.insert_one(basicData)
  except:
    print("Error attempting to create new user!")

def save(data):
  try:
    t = database.replace_one({'userid' : data['userid']}, data)
  except:
    print("Error attempting to save data")

def restore(id):
  try:
    data = database.find_one({'userid' : id})
    if data:
      return data
    else:
      print("Could not find data")
  except:
    print("Error attempting to restore data")

def checkExist(_id):
  try:
    data = database.find_one({'userid' : _id})
    if data:
      return True
    else:
      return False
  except Exception as e:
    print(str(e))

Then we can turn our attention back to the main file.

The first command I’m going to make is going to be the command to claim your smackers. This will be a command that can only be run once every thirty seconds.

@bot.command()
async def work(ctx):
	pass

The first thing we need to do is get access to the user’s data with:

user = save.restore(ctx.author.id)

Just make sure to add save as one of the imports up at the top, so we have access to the functions inside of the file we created.

The next thing we need to to is check if the user has run this command in the last thirty seconds

currentTime = time.time()
lastTime = user['listTime']
if lastTime+30 <= currentTime:
	pass
else:
	await ctx.send(f"{ctx.author.mention}, you need to wait {str(int(30-(currentTime-lastTime))} seconds"

If they have not, then we can award them their smackers, we’ll give them twenty.

user['smackers'] += 30
user['lastTime'] = time.time() # I forgot about this when I first did it and it took me ages to figure out

save.save(user) # save the changes

await ctx.send("You earned 30 smackers") # alert the user

Now let’s check it and see if it works in the server.

And it does. It did take me a while to figure out what was wrong with it, until I realized that I never saved the last time that they worked, which was the reason it wasn’t making you wait thirty seconds, but that was an easy fix once I figured it out.

Now we need a command to collect a daily reward, which will be 1000. This will have a cool down of 43200 seconds, or 12 hours.

The first thing we'll do is make the command:

@bot.command()
async def daily(ctx):
	pass

This function is going to look pretty much exactly like our work one, except we’ll change the time to 43200 seconds, and instead of telling the user this

We’ll round it down by dividing it by 60, making it minutes.

Now, so far we have a way to rack up points, but there isn’t a way to see how many of these smackers you have, let’s fix that.

Really the function should be pretty simple, all we need is a command that get’s the users data, and then to be cool displays it in an embed.

Looks like this:

@bot.command()
async def smackers(ctx):
	user = save.restore(ctx.author.id)
	embed = discord.Embed(title=f"{ctx.author.mention}'s Stuff", description=f"Smackers: `{str(user['smackers'])}`"
	await ctx.send(embed=embed)

And that should work to display the user’s data.

Our bot is looking pretty good, but there still isn’t really a use for these smackers, so let’s make a leaderboard and shop.
We’ll start with the leaderboard.

First we’ll make a function inside of the save file:

def leaderboard():
	try:
		pass
	except:
		print("Error trying to get leaderboard data")

Now inside of the try we’ll put our first line, which will get all the documents from the database in the form of a List.
If you find a document from the mongodb database without a query, it will return all the documents, so all we have to do is:

data = database.find()

Now we need to sort this data to find the top ten, we can do this with this little line of code.

dataList = sorted(data, key = lambda i: i['smackers'],reverse=True)

If you asked me what that line of code did, I would tell you that it sorts the data in ascending order by the value of smackers .
If you asked me how it did it, I would tell you that I have no idea. It just works.

Now that we have the data sorted, we take the top ten results, and then return those to the user.

results = dataList[:10]

return results

Then back inside of the main file, we make a new command, and get the list from the user. We then create an embed and fill it with the data using a for loop.

@bot.command()
async def leaderboard(ctx):
	topTen = save.leaderboard()
	
	embed = discord.Embed(title="Leaderboard", description="Top ten users by smackers")
	
	for user in topTen:
		embed.add_field(name=f"{str(topTen.index(user)+1)}. {user['userid']}", value=f"Smackers: `{str(user['Smackers'])}`")

	await ctx.send(embed=embed)

It would be almost perfect, except that it’s displaying a bunch of id’s and not names, so it’s kind of hard to see what’s going on on the leaderboard. In order to fix this, we’ll have to the username from the id, which we can do with:

discordData = await ctx.message.guild.query_members(user_ids=[user['userid']])
name = discordData[0]

To keep things tidy we’ll just make a little function to do this for us and put it at the bottom of the script just above or bot.run(TOKEN) line of code.

async def getName(ctx, id)
	discordData = await ctx.message.guild.query_members(user_ids=[id])
	name = discordData[0]
	return name

And so we’ll change our line of code adding a field to the embed to:

embed.add_field(name=f"{str(topTen.index(user)+1)}. {getName(ctx, id).name}", value=f"Smackers: `{str(user['smackers'])}`"

Now our leaderboard should be working.

We can test it to be sure, and now let’s move on to the shop.

I’m only going to have one thing in the shop, but it should show you how to do it and make it really easy for you to add more.

We start with a command as usual.
Then we will get the user’s data’s so we can make them an embed showing what they can buy and how much money they have.

@bot.command()
async def shop(ctx):
	user = save.restore(ctx.author.id)

	embed = discord.Embed(title="Shop", description=f"You have {str(user['smackers']} smackers")
	embed.add_field(name="Cool Person Role", value="`5000` Smackers | Command: `$buy "cool person"`", inline=False)
	
	await ctx.send(embed=embed)

If they think they have enough smackers they can try to buy the role, then they can use the command $buy “cool person" to try and buy it.
The function buy will take in two args, one ctx as usual and the next the next argument which should be the rest of the words.

@bot.command()
async def buy(ctx, item):
	if item.lower() == 'cool person':
		user = save.restore(ctx.author.id)
		if user['smackers'] >= 5000:
			# give them the role
		else:
			await ctx.send(f"You need {str(5000-user['smackers'])} more smackers to buy 'cool person'"
	else:
		pass

Now how to give them the role, it actually took me a while to figure this one out, as I usually don’t deal with roles and stuff, but here’s how to do it.

member = await ctx.message.guild.query_members(user_ids=[ctx.author.id])
  member = member[0]
  await member.add_roles(discord.utils.get(member.guild.roles, name="Cool Person")) 

First we get the member data, and then we add the role to that member, it’s actually more simple now that I’ve figured it out. One thing that you will need however, is to import discord.utils at the top of your code, so you have access to it’s functions.
You will also need to have the role already created in your server of course, with the name matching the one in your code.

And finally after giving them the role, we can send them a message to let them know that they got it.

await ctx.send(f"{ctx.author.mention} now has 'Cool Person' role becuase they are a cool person.")

And don’t forget to save the fact that they no longer have 5000 smackers

save.save(user)

We can test everything out to see if it works, and now we have a working discord bot with a little currency system, a tiny shop (you can add more stuff) as well as some cool little features.

Keeping it alive

Ok, I have the hacker plan, at least for now. So I could just turn the repl on “Always on” but always on actually shuts down every now and again, and a lot of people don’t have the hacker plan anyways.
So, how do you get your discord bot to always be on? By using uptimeRobot, a site you can use to ping your repl every certain amount of time to make sure that it stays on.

The first thing we need to do to get this done is add another python file to our repl, I called it keep_alive.py, for obvious reasons.
Inside the file you put this:

from flask import Flask
from threading import Thread

web = Flask('')

@web.route('/')
def home():
   return "I am alive!"

def run():
  web.run(host='0.0.0.0',port=8080)

def keep_alive():
   run_thread = Thread(target=run)
   run_thread.start()

This is basically just a little flask page. And then inside of our main.py we put keep_alive.keep_alive() right before or bot.run(TOKEN) line.
And of course make sure to import keep_alive at the top of the repl.

Now we need to got to here: UptimeRobot | Free Website Monitoring

Login if you already have an account or if you don’t sign up.

Once logged in you should see a page like this:

Then click on Add new Monitor.

Select a monitor type as HTTP(s) and give it a nickname, then put in the url for your repl, which you can find here:

After everyone is filled out you can click on Create Monitor, and now your repl should be pinged every five minutes to make sure that it keeps awake.

Note

I did run into a lot of bugs working on this tutorial and so some of the code here might be wrong (I’m pretty sure I forgot to put await before some of my messages)
But all the code in the attached repl has been tested and works, and if you want to see for yourself, you can come over to my test server and mess around with it, or put the bot in your own server.

Server link: InvisibleOne’s Test Server
Link to add bot: Discord

And that’s all for this tutorial, if you have any questions, things you want me to fix or stuff I could do better, let me know.

Comments
hotnewtop
RowenaRavenclaw (0)

I have been looking for a tutorial that reviews adding bot commands that post a .png or .jpg, are there any tutorials with this information?

InvisibleOne (3227)

Just send an image? All you need to do is have the image hosted somewhere and sen the URL. @RowenaRavenclaw

InvisibleOne (3227)

np, let me know if you need help @RowenaRavenclaw

RowenaRavenclaw (0)

I was able to get it working properly with the URL, however i've run into another issue. I'm looking to have 3 separate images posted using one command, and keep receiving an error. Any suggestions? @InvisibleOne