Our Discord Bots
As we grow our online Discord community, we also seek to customise it to our needs and liking. So, we wrote two Discord bots:
And this blog post will tell you all about them. All our bots are open sourced and you can find them on our GitLab project. They are written in Python, using the Discord.py API. Our bots are designed to work on one server. SassBot works well on multiple servers, with a common bank of sassy replies. But AFNOM is tied to one guild. It would not be a massive task to adapt them for multiple servers use, with distinct replies or users management, but we did not need this, so we didn’t go that far.
SassBot
When we migrated from our Slack space to Discord, we found we very much missed one Slack feature: Slackbot. We used Slackbot to quip in with witty replies to keywords we pre-defined. Unfortunately, we did not find a suitable Discord bot to replace Slackbot. So we wrote our own! Enter: SassBot.
How it works: SassBot is a fairly simple bot, which has a bank of keywords and a list of replies associated with the keyword (a dictionary). Whenever a user types a keyword in a channel, SassBot will randomly choose a reply (if it has multiple), and send it on that channel. The dictionary of keywords is stored on disk as a pickle file, for persistence, and gets updated whenever a new keyword/response is added or deleted. If the bot goes offline, the dictionary is not lost.
You can add a keyword - response pair by issuing the command ?add keyword
response
, e.g.:
?add "web chall" "Have you checked `robots.txt`?'"
When a keyword is added, the bot creates and stores a regex pattern:
p = re.compile(r'\b' + r'{}'.format(keyword) + r'\b', re.IGNORECASE)
This is then used to check if any message sent on a channel matches the regex pattern. If so, it stops on the first keyword match and randomly chooses a witty reply.
for k, v in PATTERNS.items():
m = v.search(message.content)
if not m is None:
reply = random.choice(SASS[k])
await message.channel.send(reply)
break
You can also view the registered key pairs with ?list
:
We do like our Mac/iPhone/iOS jokes, as well as being reminded that robots.txt
exists, when it comes to web challenges. We have forgotten one too many times
about it in CTFs 😄.
We can also delete keywords, along with all their replies, e.g.:
?deleteK "web chall"
or delete just one reply from a specific keyword, by providing the keyword and the reply string to be removed, e.g.:
?deleteR macos "Have you considered Linux?"
We also restricted adding and removing sassy quips to a few roles (specified in
the control_roles
list).
Improvements: As it stands, the bot matches the first keyword it finds in the dictionary to a user message content. Therefore, if a message has multiple keywords, it will match the oldest registered keyword. The bot could be modified to find all matching keywords, and then randomly choose one keyword to reply to. We’ll work on that.
GitLab project: SassBot
AFNOM
One of the things we wanted for our community is a way to verify a user is a University of Birmingham student or staff, as we are, after all, a UoB society. With Slack, it was easy to use the domain allow list to specify that users need to register a Slack account with their @bham email. However, on Discord, a user exists across multiple servers, and there is no allow/deny domain control.
We could not find a suitable bot to do this verification for us, so we wrote our own (again)! Enter: AFNOM (very confusing name indeed).
AFNOM bot is slightly more complex than SassBot, as it needs a few extra bits and pieces: a database and the ability to send emails.
How it works: AFNOM bot performs user verification by generating a random
code of the form xxxx-yyyy-zzzz
and sends it to a member’s UoB email. The
user has to specify the UoB email user. Once the email is received, the user
gives the bot back the code. If the code matches to the one previously generated,
the user is upgraded to the verified role, which gives them access to the whole
server.
To start off the process, a user has to have the guest role (which is obtained
by a reaction role, signifying agreement to our Code of Conduct). The verification process is started by issuing the
++verify
command. The bot will send a direct message to the user:
Next, the user needs to supply their University of Birmingham email username.
This is usually in the form abc123
. The bot will automatically append @bham.ac.uk
to the given username, and send an email to that address, with the random code
(screenshot edited for privacy reasons).
The email with the code should be received:
And the verification process can be completed by supplying the code to the
bot, in a DM, with the command ++code xxxx-yyyy-zzzz
. The bot will confirm
if the process was successful:
If the code supplied does not match, the bot reminds the user the code is case sensitive:
Tech deets: The bot uses a database (we use mariadb) to store the details of the users, both during the process and afterwards.
The database has two tables:
MariaDB> show columns from register;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| discordUser | varchar(255) | YES | | NULL | |
| emailUser | varchar(255) | YES | | NULL | |
| code | varchar(255) | YES | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
MariaDB> show columns from verified;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| discordUser | varchar(255) | YES | | NULL | |
| emailUser | varchar(255) | YES | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
The register
table holds information during the process. We store
a hash (sha256) of the discord user issuing the ++emailuser
command,
a hash of the email username they supply, and the generated code. Entries
are removed from this table once the verification process has been completed.
The verified
table stores a hash of the discord user that has successfully
verified (after the ++code
command) and a hash of the email username they
used.
This allows us to prevent multiple users from verifying with the same email username.
Also, if a user is already in the database as verified
and, for any reason, they lose their verified role, they can regain
it with only the ++verify
command:
In order to send emails, we use the python smtplib module (we found this tutorial very useful!), and our email provider smtp server details.
Update: The command ++delete
removes the entries of the user initiating
it from both verified
and register
tables. The command can be used if
the email user given to the bot is wrong (and therefore registration cannot
be completed) or if a person simply does not want their data stored in our
database!
Improvements: The bot can only upgrade roles on one server (specified
in the GUILD
variable). It can easily be modified to handle multiple servers
and even automatically apply some role to a new server member, if they have
already authenticated on a different server. But this is a project for a
different day 😄.
GitLab project: AFNOM
If you find any flaws in our bots or would like to contribute to them, please feel free to make a pull request on their respective projects.