Motivation

I’ve wanted to learn Rust for quite some time and read multiple tutorials, but for me, the best way to learn a new language is to actually use it. So, I was looking for a project to work on, and I found it in the form of a Discord bot.
I’m part of a medium-sized server (~100 people), and we occasionally encountered tedious things where it’s clear: this is something a bot should do.

Tech-Stack

So, I picked a framework to build a Discord bot in Rust and chose Serenity. It’s a really nice and well-documented library that makes it easy to interact with the Discord API. I quickly got a bot up and running and got to adding features.
At first, this worked well, but Serenity is a rather low-level API, and it showed. I found myself writing a lot of boilerplate code to implement, for example, slash commands, so I looked around, and low and behold: the author of Serenity has had the same problem and wrote a high-level library. It’s called Poise and builds on Serenity, so the transition was quite easy. Now, I could start implementing everything I want.

I didn’t want to set up a server, and I had none lying around, so I did a quick search and decided to go with Shuttle. It had just been launched at the time, but the community is nice and helpful, and they’re rapidly making progress. I’m really happy with the service and can only recommend it.

This also solved another problem: I needed a way to store data. I initially tried to manage the data with a local SQLite Database, but it quickly became clear this wasn’t ideal.
Luckily Shuttle comes with two types of Databases: (now paid) AWS Relational Database Service and Shared Databases. I first used an AWS RDS but then migrated to a Shared Database. For my admittedly small use case, I didn’t notice a difference, and I don’t have to worry about the maintenance of the database.

The database is a Postgres database, and I use the sqlx crate to interact with it. This crate provides compile-time query checking, which is really helpful for catching both errors and typos.

Feature: Copying Emojis

As a new server, we wanted custom emojis from other servers. We could buy Discord Nitro, but everyone would have to do that and be on all the other servers. We could also download all the emojis from the servers and upload it to ours, but that’s very tedious.
So, I wrote a command to copy emotes from one server to another. Discord identifies almost everything with Snowflake IDs, and emojis are no exception. When you send a custom emoji, the message does not contain the image. Discord instead sends and stores <:name:snowflake>. For animated emojis, an a' is added in the beginning. The actual image is stored on https://cdn.discordapp.com/emojis/.`. `` can be any of `png`, `jpg` or `webp` for static and `gif` for animated emojis. See this example: `<a:pop:1066979518255988796>` ![a cat opening and closing its mouth](https://cdn.discordapp.com/emojis/1066979518255988796.gif)

Now equipped with this knowledge, we can take any text from a user and search for this emoji pattern. Then, we can download all the emojis’ image files and upload them to our server. For convenience, I also added a command that takes a message and extracts the emojis from its content or even its reactions. When a Nitro user reacts with a lovely emoji, we can easily put it on our server.

a Discord embed with a picture of a custom emoji asking "Create 1 emoji(s)?", below it two buttons "Add" and "Cancel"

As a side note, I also replicated the upload, rename, and delete functionality, so if you like commands better than Discords UI, you can manage your emojis entirely with the bot.

More Features

With that said, I’ve added a lot of other features since then, and I will talk about the most interesting or tricky ones later.
In the meantime, you can find the entire source code on GitHub, although I still need to decide on a license. Feel free to open an issue or leave a comment if you have any suggestions for which one to pick.
See you in the follow-up, and have a great day!

Comments