A pure-Rust serverless discord chatbot hosted on Cloudflare Workers. With a free account you have up to 100k requests per day. For storing state you can use the bundled workers-rs
crate to access KV or Durable objects.
This template is designed for compiling Rust to WebAssembly and publishing the resulting worker to Cloudflare's edge infrastructure.
- Signup for a Cloudflare account, in the dashboard setup a subdomain (i.e
<mydomain>.workers.dev
) - Setup a worker project named
bot
(i.ebot.<mydomain>.workers.dev
) or pick your own name and update wrangler.toml - Install wrangler CLI with
cargo install wrangler
and authenticate with cloudflare viawrangler config
- Create a new discord app at https://discord.com/developers/applications and copy your token/application_id/public_key
- Pass those secrets to your bot with
wrangler secret put DISCORD_TOKEN
,wrangler secret put DISCORD_PUBLIC_KEY
,wrangler secret put DISCORD_APPLICATION_ID
- Add bot permissions and grab your Oauth url to invite the bot to your server
- Publish the demo app with
wrangler publish
. The template bot contains a single hello command with a dummy autocomplete argument. - Put your bot domain
https://bot.<mydomain>.workers.dev
in theINTERACTIONS ENDPOINT URL
in your discord app page from step 4 - After initial deployment and each time you add a new command on your bot you need to register it with the discord api. To do that simply
curl -X POST https://bot.<mydomain>.workers.dev/register
You should now be able to run the /hello
command on discord
To add a new command simply implement the Command
trait. For example to add a ping command
- create a file src/commands/ping.rs
use crate::interaction::{
InteractionApplicationCommandCallbackData, ApplicationCommandOption, ApplicationCommandOptionChoice, ApplicationCommandOptionType
};
use crate::error::InteractionError;
use crate::command::{Command, CommandInput};
use async_trait::async_trait;
pub(crate) struct Ping {}
#[async_trait(?Send)]
impl Command for Ping {
async fn respond(&self, _input: &CommandInput) -> Result<InteractionApplicationCommandCallbackData, InteractionError> {
Ok(InteractionApplicationCommandCallbackData {
content: Some("Pong".to_string()),
choices: None,
embeds: None
})
}
fn name(&self) -> String{
"ping".into()
}
fn description(&self) -> String {
"Send a ping".into()
}
fn options(&self) -> Option<Vec<ApplicationCommandOption>> {
// add any arguments/choices here, more info at https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure
None
}
async fn autocomplete(&self, _input: &CommandInput) -> Result<Option<InteractionApplicationCommandCallbackData>, InteractionError> {
None
}
}
- add your new module in src/commands/mod.rs
- Register your command in
init_commands
in src/command.rs
pub(crate) fn init_commands() -> Vec<Box<dyn Command + Sync>> {
let mut v : Vec<Box<dyn Command + Sync>> = Vec::new();
v.push(Box::new(commands::hello::Hello {}));
// Add this line
v.push(Box::new(commands::ping::Ping {}));
v
}
- publish your package with
wrangler publish
- register your new command with discord with
curl -X POST http://bot.<mydomain>.workers.dev/register
You can store and access state using the input
context object passed to the respond
and autocomplete
methods, for example:
let my_val = input.kv_get("my_namespace", "my_key").await?; // the namespace must be first registered on cloudflare dashboard
input.kv_put("my_namespace", "foo", "bar").await?;
With wrangler
, you can build, test, and deploy your Worker with the following commands:
# compiles your project to WebAssembly and will warn of any issues
wrangler build
# run your Worker in an ideal development workflow (with a local server, file watcher & more)
wrangler dev
# deploy your Worker globally to the Cloudflare network (update your wrangler.toml file for configuration)
wrangler publish
you can use ngrok
to tunnel traffic into your local machine, more info here
You can create an a action to automatically deploy your worker & register your commands on each push to main.
Create a Cloudflare API token and use the Edit Cloudflare Workers
template.
Then, create a repository secret with your API Token under CF_API_TOKEN
and add the following inside .github/workflows/deploy.yml
:
name: Deploy to Cloudflare Workers
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
- uses: actions/checkout@v3
- name: Deploy to Cloudflare Workers
env:
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
run: npm i -g wrangler && npx wrangler publish
- name: Curl the worker
run: curl -X POST https://<your_worker_domain>/register
workers-rs
(the Rust SDK for Cloudflare Workers used in this template) is meant to be executed as
compiled WebAssembly, and as such so must all the code you write and depend upon. All crates and
modules used in Rust-based Workers projects have to compile to the wasm32-unknown-unknown
triple.
Read more about this on the workers-rs
project README.
based on stateless-discord-bot