Introduction
Whether you're a community manager or part of a collaborative team, staying on top of conversations can be challenging. What if you could automate the process and generate concise, Markdown-based summaries of your server's activity every day? That's exactly what we're going to build in this tutorial!
In this guide, we'll create a Discord bot that listens to your server, collects messages from the previous day, and generates a neatly formatted Markdown summary. The summary will highlight key points and group messages by topics and authors. This bot will save time, improve organisation, and make it easier to follow conversations. We'll also be using the new DeepSeek R1 model to generate our summaries with Hyperbolic, an AI cloud provider that provides GPU renting services as well as an AI inference API.
Interested in checking out the full example? Find it here.
Pre-Requisites
Before we get started, you'll need to make sure you have the following installed:
- the Rust programming language
- The
cargo-shuttle
CLI tool (for deploying to Shuttle & project initialisation) - You will also additionally need a Hyperbolic API key. To get one, follow the instructions below:
- You'll need to make a Hyperbolic account from the application.
- Once registered, navigate to the Settings page on the dashboard.
- You'll be able to view and copy your Hyperbolic API Key from there. Keep ahold of your API key as we'll be using it later.
- A Discord API key is also required. If you don't already have one, follow the instructions to get one (it's totally free!):
- Click the New Application button, name your application and click Create.
- Navigate to the Bot tab in the lefthand menu, and add a new bot.
- On the bot page click the Reset Token button to reveal your token. Keep ahold of this token as we'll be using it later.
- For the sake of this example, you also need to scroll down on the bot page to the Message Content Intent section and enable that option.
- You will also need
sqlx-cli
installed (the SQLx CLI tool) which will allow you to easily manage SQL migrations versions.
Getting Started
To get started, we will spin up a framework boilerplate that deploys a bot using the serenity
Discord bot framework:
This will create a template with the following:
- A
Bot
unit struct that has a basicEventHandler
implementation - A
main
function that sets up aserenity
client for automatic deployment to Shuttle
You'll also notice that a Secrets.toml
file has been created. We'll extend it to include the Hyperbolic API key & Discord we obtained before. Note that you will also need a Discord channel ID where you want your bot to send the reports to - you can select a channel by simply right clicking it and getting the channel ID.
Adding crate dependencies
Before we continue, let's add our crate dependencies. You can add all the required dependencies by copying the one-liner below:
Let's closely examine what our new dependencies are for:
- rig-core: The
rig
framework. - sqlx: A library for working with SQL. We add the
runtime-tokio-rustls
andpostgres
features (both are mandatory), as well as themacros
andchrono
features for enabling usage with thechrono
crate. - shuttle-shared-db: A crate that allows provisioning of a Postgres database from Shuttle servers (and locally, Docker). We can allow it to output a connection pool from the Shuttle resource annotation
- chrono: A crate for dealing with time.
- serde-json: A crate for (de)serializing to and from JSON.
Let's Build!
Migrations
Before we do anything, we need to create our migration table. Let's create our first migration - we'll make it reversible:
This creates a folder called migrations
in your project root and additionally creates an up
and down
file for creating and reversing migrations, respectively.
We'll want to store both received messages, as well as summaries:
Next, we'll set up our down
file which will reverse the migration. You should not need this in most cases, but in case you want to drop the table for whatever reason (e.g. during development or testing), you can do so:
Storing Messages
To store messages, we will upgrade our Bot
struct (which acts as the event handler struct) to additionally include our PgPool
. When we implement EventHandler
for our struct, we will then be able to access the database pool to make insertion queries.
Next, we will make it so that any and all messages created will be stored in our Postgres instance as a JSON object - we will adjust our impl EventHandler for Bot
block to simply convert the message into a raw JSON string then store it.
That's pretty much it for the Discord bot interactions at a basic level. Nothing else required! Note that this is a relatively naive implementation. If you wanted to improve this, a good way to do so would be to have some kind of durable message queueing to ensure that there is no information loss.
Creating a Summarization Agent
This part is fortunately quite simple. For our summaries, we only need to implement a single AI agent that summarizes all the messages. We then return the result.
Creating and sending summaries
Now for the fun part - creating and sending summaries (to a Discord channel of our choosing!). We'll split this into a couple of separate functions:
- One for generating the report itself (so that we can extend it to be used elsewhere other than the scheduled task that sends generated reports to a Discord channel)
- One for running a scheduled task (that carries out report generation & sending)
The other half of this is creating our loop for automatically sending summaries. To ensure that the loop properly executes the task on time, we use tokio::time::Interval
which is more accurate compared to simply just using the tokio::time::sleep()
method.
Hooking it all back up
The first time we need to do is to add our Postgres
annotation from shuttle-shared-db
- to do so, we simply add it as a function argument to our main function (shown as annotated by the runtime macro):
Running your program locally will now use Docker to provision a Postgres database. Additionally, when deployed the Shuttle servers will automatically provision a Postgres database for you using the shared cluster.
Note that there's a return type here - ShuttleSerenity
. We don't need to run the Discord bot manually because the runtime does this automatically for us - instead, we return the Discord bot struct using .into()
- this will be illustrated later on.
Next, we need to get our secrets from the SecretStore
(i.e., our Secrets.toml
file that we created earlier). We also need to parse our channel ID into a u64
as this will then allow us to automatically convert it into a serenity::all::ChannelId
, which we need to then use for sending messages to with a Discord HTTP client.
Finally we will set up Discord bot and scheduled task, then return the Discord bot client, then return the bot (note that ShuttleSerenity
implements From<Client>
which is why we can use .into()
here):
Deploying
Now that we've written all of the code, we can just use shuttle deploy
and watch the magic happen!
Note that we're not using a web service framework - trying to reach the deployment URL will simply return with a 404.
Finishing up
Thanks for reading! Hopefully you have found this useful. While AI assisted applications aren't quite ready to do the dishes yet, they can certainly be quite helpful in a number of ways.