The icon is from yuilero.
Welcome to HollowBot's repository! Here you can find everything about the bot and some steps to start building your own. Enjoy!
- Description
- Add it into your Discord server
- How to create your own?
- Bot files
- Use cases diagram
- Database structure
- PYDOC Documentation
A simple discord bot which plays with you! Feel free of reading the docs or creating your own version.
The bot can only execute the command '--info' outside of a channel called 'playground'.
--play <- This command starts a new game.
--wins <- This command makes the bot say your global high score.
--info <- This command sends a DM to the caster with basic information about the bot.
The icon is from yuilero, you can visit her original post by clicking here.
Here are some tutorials and pages that helped me a lot to create this Discord bot:
- w3schools
- Discord Api documentation
- freeCodeCamp
- RealPython
- Lucas Youtube tutorial
- Code With Swastik Youtube tutorial
- stack overflow
- MySQL introduction
- MySQL tutorial
- Emojipedia
Click here to make a new friend!
If you'll work locally, you'll need to prepare this previously, if you are working in a cloud IDE, like Repl.it, just import the necessary packages into your workspace:
- Discord account
- Python installed (v3.8.5+)
- Pip installed (v20.0.2+)
- Discord.py installed (v1.5.1+)
- MySQL Connector installed (v8.0.22+)
- Dotenv installed (v0.15.0+)
This is not completely necessary, you may need one or none, depending on what you want to develop and achieve:
- Git installed
- Repl.it account
- UptimeRobot account
- Flask Installed (v1.1.2+)
- Visual Studio Code installed
- MySQL Workbench installed
If you speak Spanish and you want to follow the simple steps with a video, you can click here.
- Creating a Bot
First of all, we are going to the Discord Developer Portal to create our Discord Bot.
At this moment we have to be in the following page:
Now, click the "New Application" button:
Put a name and create it, and voalá, you have created your first application:
But, it's not a bot yet, go to the section "Bot" of the side bar and transform it:
Now, it's time to add it into a Discord server. Go to the section "OAuth2" of the side bar and select the scope 'bot':
Then, give it some basic permissions for now:
After that, copy the link that appeared in the Scopes table and put it in your browser. You'll see a window that asks you if you want to add your recently created bot into a channel, choose one and continue:
And you did it! You added your bot into a channel:
- Bring it to life
IMPORTANT!: To bring it to life, you'll need its token, but be very careful, you cannot put a token anywhere, because if someone gets it they can change the bot's code, and that could be very dangerous!
To get your bot's token, return to the Discord Developer Portal, select your bot and copy it:
Well, it's time to open your favorite IDE and write some code. At first, open a folder, and create there two files, a main.py
and a .env
.
As I said to you, we need to keep secret our token, so, we can use "environment variables" to achieve that (in case you are saving all in a GitHub repository, add a .gitignore
file and write inside .env
), because it could be accessed easily in the code and can't be visible in most of the web repositories/IDE's (like Repl.it).
Into your .env
file, write the following:
TOKEN=your_token_here
And now, open your main.py
(or the name you liked) to start the creation of your bot. First, we need to import some packages:
import discord # Discord api
from discord.ext import commands # Discord command extension
import os # Operative system package, for searching use
from dotenv import load_dotenv # Package which help us to find the environment variable
Right now, we still doing nothing with the bot, let's try the following:
import discord
from discord.ext import commands
import os
from dotenv import load_dotenv
# This creates an object that can manage
# the bot's commands by using a prefix.
prefix = '--'
client = commands.Bot(command_prefix=prefix)
@client.event # Decorator
async def on_ready():
# This is an coroutine event, and this in special
# can only be activated when the bot is initialized.
print('We have logged in as {0.user}'.format(client))
# Load environment variables
load_dotenv()
# client.run('[token]') <- Start the bot which has the
# following token
# os.getenv('TOKEN') <- Operative system, get the
# environment variable called TOKEN
client.run(os.getenv('TOKEN'))
# Note: You can freely delete all comments in your deploy.
If you run the last code in your computer, you'll be able to read this in your command line, and, see your bot becoming active (you can stop the process by simply killing the terminal):
Hey! We are closer to make our bot a little bit more responsive, what if we add it a little command?
import discord
from discord.ext import commands
import os
from dotenv import load_dotenv
prefix = '--'
client = commands.Bot(command_prefix=prefix)
# Events
@client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
@client.event
async def on_message(message):
# Event activated when a message is sent by anyone
if message.author == client.user:
# If the message is from the bot, don't continue
return
# Call the activation of commands
await client.process_commands(message)
# Commands
@client.command()
async def ping(ctx):
# The function's name is the keyword of your command!
# It gets activated when the user enters [prefix]say,
# ie. --ping
await ctx.send('Pong!')
load_dotenv()
client.run(os.getenv('TOKEN'))
Cool, isn't it? if you enter "--ping" the bot will answer you "Pong!":
But, wait, what is ctx? Ctx means context, and every command needs to get the context of the call.
And, you may question yourself, what happens if a user sends a command that doesn't exist? In simple words, nothing, you'll see an error message in command line, but (for good practices) we need to manage this kind of errors:
import discord
from discord.ext import commands
import os
from dotenv import load_dotenv
prefix = '--'
client = commands.Bot(command_prefix=prefix)
# Events
@client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
@client.event
async def on_message(message):
if message.author == client.user:
return
await client.process_commands(message)
@client.event
async def on_command_error(ctx, error):
# Event which only get activated when getting an error
if isinstance(error, commands.CommandNotFound):
# Here we are creating an embed message to notify the error
embed = discord.Embed(
title='Oops!',
description='Error: {}'.format(error),
colour=discord.Colour.from_rgb(61, 64, 91)
)
await ctx.send(embed=embed, delete_after=10.0)
# Commands
@client.command()
async def ping(ctx):
await ctx.send('Pong!')
load_dotenv()
client.run(os.getenv('TOKEN'))
Try out a non existing command, like '--test':
- Modularity
To divide the program into multiple small parts, which increase readability and maintainability, that do specific tasks we can use Cogs extension.
But, what we could divide and how? My friend, we can make the commands a different part of the main code, making a cleaner work and making easier to include more commands! Follow me and see how:
First, create a subdirectory and add two new python files there, you will have something like this:
- src
+ main.py
+ .env
- modules
+ ping.py
+ say.py
And, in the main.py
file we are going to add a "reading" process and delete the command '--ping' (save it for ping.py
):
import discord
from discord.ext import commands
import os
import sys #new
from dotenv import load_dotenv
# Initialization
prefix = '--'
client = commands.Bot(command_prefix=prefix)
# Reading process
# NOTE: If you are running this from vs code directly, it may fail, do
# the execution manually from the terminal!
# Why it may fail? Since VS Code will try to execute the program from a
# directory that is 'inside' the file, this process will never find the
# subdirectory.
# If you have this problem, set your terminal (by cd command)
# in the source ('src') directory.
for filename in os.listdir('modules/'):
# os.listdir('PATH') returns a list with all items inside the path
if (filename.endswith('.py')):
# ^ If it is a python file, continue the process...
# load_extension adds the code from the file to the main program
# during the execution, it's similar to importing, but you don't
# have to cast the object to check if the command inside of it
# was called.
client.load_extension('modules.{}'.format(filename[:-3]))
# Events
@client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
@client.event
async def on_message(message):
if message.author == client.user:
return
await client.process_commands(message)
@client.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
embed = discord.Embed(
title='Oops!',
description='Error: {}'.format(error),
colour=discord.Colour.from_rgb(61, 64, 91)
)
await ctx.send(embed=embed, delete_after=10.0)
load_dotenv()
client.run(os.getenv('TOKEN'))
Then, go to ping.py
file and write the following code:
import discord
from discord.ext import commands
class Ping(commands.Cog):
def __init__(self, client):
self.client = client
# Event
@commands.Cog.listener()
async def on_ready(self):
# If this module is charged, will print it
print('Ping module loaded!')
# Command
# aliases adds new kewwords to your command
@commands.command(aliases=['PING'])
async def ping(self, ctx):
# Add a reaction to the command ¬
await ctx.message.add_reaction('🏓')
await ctx.send('Pong!')
# Needed to initialize
def setup(client):
client.add_cog(Ping(client))
And finally, go to say.py
:
import discord
from discord.ext import commands
class Say(commands.Cog):
def __init__(self, client):
self.client = client
# Event
@commands.Cog.listener()
async def on_ready(self):
# If this module is charged, will print it
print('Say module loaded!')
# Command
@commands.command(aliases=['SAY'])
async def say(self, ctx, *, message=""):
# Say the words that follow the command
await ctx.send(message)
# Needed to initialize
def setup(client):
client.add_cog(Say(client))
And now, it's time to try our new bot:
- Saving score
At this moment you know the basics of creating Discord bots, by now you can try using the code that I made for HollowBot and adding them your own commands into the modules.
Note that HollowBot saves and retrieves the score by using the class Connector, that also needs some environment variables, you have to update them:
TOKEN=your_token_here
HOST=your_host_here
MYSQLUS=your_user_name_here
PASSWD=your_password_here
DB=your_database_name_here
In case you can't or don't want to use a database, replace the following code from Play.py
:
if (flagDoSave):
try:
c = Connector()
c.updateScore(str(ctx.author.id))
except Exception as e:
embed = discord.Embed(
title='Oops!',
description='Sorry, but I can\'t save your victory in my Legends Journal, I think I lost my pen...',
colour=discord.Colour.red()
)
await ctx.send(embed=embed, delete_after=10.0)
To this:
if (flagDoSave):
try:
# 'a' means append into the file
with open('wins.txt', 'a') as wins:
wins.write(str(ctx.author.id) + '\n')
except Exception as e:
embed = discord.Embed(
title='Oops!',
description='Sorry, but I can\'t save your victory in my Legends Journal, I think I lost my pen...',
colour=discord.Colour.red()
)
await ctx.send(embed=embed, delete_after=10.0)
And the following code from Wins.py
:
score = -1
try:
c = Connector()
score = c.getWins(str(ctx.author.id))
except Exception as e:
score = -1
To this:
score = 0
try:
# 'r' means read the file
with open('wins.txt', 'r') as wins:
for line in wins:
temp = line.split('\n')
if (temp[0] == str(ctx.author.id)):
score += 1
except Exception as e:
score = 0
Then you can leave your environment variables file the same as it was at the beginning:
TOKEN=your_token_here
And finally, do not forget to delete the imports to Connector.py
, and do not include that file in the modules directory.
All of this will change the way of storing your data, at this moment and forward the bot will create a wins.txt
file in which, everytime a user wins the game, will append their id instead of saving the victories amount in a database.
- Hosting your bot
And last but not least, hosting. I feel like the method that used freeCodeCamp in their tutorial is the easiest and free way to host our little creation, you can find all the instructions there:
Icons designed by Flat Icons from Flaticon, and background picture by Roberto Nickson from Pexels.
The database only has one table which stores the user id of a player and their global score.