Nov 8, 2020 • neko3

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.

sassbot help

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:

sassbot dictionary

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.

afnom help

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:

afnom verify dm

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).

afnom email dm

The email with the code should be received:

afnom email

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:

afnom verified

If the code supplied does not match, the bot reminds the user the code is case sensitive:

afnom codes mismatch

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:

afnom regain verified

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!

afnom delete user

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.