Writing an example bot
Create a new project:
cargo new --bin mybot
Add robespierre, robespierre-cache, robespierre-http, robespierre-events, and robespierre-models:
[dependencies]
robespierre = { version = "0.3.0", features = ["cache", "events", "framework", "framework-macros"] }
robespierre-cache = "0.3.0"
robespierre-http = "0.3.0"
robespierre-events = "0.3.0"
robespierre-models = "0.3.0"
We'll also need tokio, and if you want logging, tracing:
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.2" # not necessarely this but some global subscriber is required for logging
// src/main.rs fn main() { println!("Hello, world!"); }
Let's start with a stub main, running on tokio:
// src/main.rs #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { Ok(()) }
Optional: initialize a global tracing subscriber:
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing_subscriber::fmt::init(); Ok(()) }
Now, get the bot token:
// src/main.rs #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); Ok(()) }
Create an authentication:
// src/main.rs use robespierre::Authentication; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // --snip-- tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); // --snip-- Ok(()) }
Then a http client:
// src/main.rs use robespierre::Authentication; use robespierre_http::Http; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // --snip-- tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); let http = Http::new(&auth).await?; // --snip-- Ok(()) }
And a websocket connection:
// src/main.rs use robespierre::Authentication; use robespierre_events::Connection; use robespierre_http::Http; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // --snip-- tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); let http = Http::new(&auth).await?; let connection = Connection::connect(&auth).await?; // --snip-- Ok(()) }
Now let's write a basic event handler (similar to how they work in serenity):
// src/main.rs use robespierre::Context; use robespierre::Authentication; use robespierre_events::Connection; use robespierre_http::Http; use robespierre_models::events::ReadyEvent; // --snip-- #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); let http = Http::new(&auth).await?; let connection = Connection::connect(&auth).await?; Ok(()) } #[derive(Clone)] struct Handler; #[robespierre::async_trait] impl robespierre::EventHandler for Handler { async fn on_ready(&self, ctx: Context, ready: ReadyEvent) { tracing::info!("We're ready!"); } }
And create the context and handler:
// src/main.rs use robespierre::Context; use robespierre::Authentication; use robespierre_cache::CacheConfig; use robespierre_events::Connection; use robespierre_http::Http; use robespierre_models::events::ReadyEvent; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // --snip-- tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); let http = Http::new(&auth).await?; let connection = Connection::connect(&auth).await?; let context = Context::new(http, robespierre::typemap::ShareMap::custom()); // (*) // and if you want a cache: let context = context.with_cache(CacheConfig::default()); let handler = Handler; Ok(()) } #[derive(Clone)] struct Handler; #[robespierre::async_trait] impl robespierre::EventHandler for Handler { async fn on_ready(&self, ctx: Context, ready: ReadyEvent) { tracing::info!("We're ready!"); } }
*) More on this in the user data chapter, for now just leave it as it is.
Now let's "run" the connection, listening to events and handling them:
// src/main.rs use robespierre::EventHandlerWrap; use robespierre::Context; use robespierre::Authentication; use robespierre_cache::CacheConfig; use robespierre_events::Connection; use robespierre_http::Http; use robespierre_models::events::ReadyEvent; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // --snip-- tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); let http = Http::new(&auth).await?; let connection = Connection::connect(&auth).await?; let context = Context::new(http, robespierre::typemap::ShareMap::custom()).with_cache(CacheConfig::default()); let handler = Handler; let handler = EventHandlerWrap::new(handler); // explained in a bit connection.run(context, handler).await?; Ok(()) } #[derive(Clone)] struct Handler; #[robespierre::async_trait] impl robespierre::EventHandler for Handler { async fn on_ready(&self, ctx: Context, ready: ReadyEvent) { tracing::info!("We're ready!"); } }
First, we have to make a distinction between the 2 types of handlers availabe:
T: EventHandlerT: RawEventHandler
The EventHandler contains functions like on_ready, on_message, on_message_update,
while the RawEventHandler only contains the function async fn handle(self, context, event),
which is supposed to handle any type of event the server sends.
Now, Connection::run takes a RawEventHandler, but our Handler is an EventHandler, not a RawEventHandler.
The EventHandlerWrap is an utility to convert the events from a RawEventHandler to call the functions of an EventHandler.
It itself is a RawEventHandler.
Now, since we used a cache in the context, we also have to update it when we receive events.
That can be done by using another utility, robespierre::CacheWrap.
robespierre::CacheWrap is a RawEventHandler, and also forwards the events to another RawEventHandler after it updates the cache.
To use it:
// src/main.rs use robespierre::CacheWrap; use robespierre::EventHandlerWrap; use robespierre::Context; use robespierre::Authentication; use robespierre_cache::CacheConfig; use robespierre_events::Connection; use robespierre_http::Http; use robespierre_models::events::ReadyEvent; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // --snip-- tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); let http = Http::new(&auth).await?; let connection = Connection::connect(&auth).await?; let context = Context::new(http, robespierre::typemap::ShareMap::custom()).with_cache(CacheConfig::default()); let handler = Handler; let handler = CacheWrap::new(EventHandlerWrap::new(handler)); connection.run(context, handler).await?; Ok(()) } #[derive(Clone)] struct Handler; #[robespierre::async_trait] impl robespierre::EventHandler for Handler { async fn on_ready(&self, ctx: Context, ready: ReadyEvent) { tracing::info!("We're ready!"); } }
Now to finish, let's make the bot reply with "pong" whenever someone says "ping":
// src/main.rs use robespierre::CacheWrap; use robespierre::EventHandlerWrap; use robespierre::Context; use robespierre::Authentication; use robespierre::model::MessageExt; use robespierre_cache::CacheConfig; use robespierre_events::Connection; use robespierre_http::Http; use robespierre_models::channels::Message; use robespierre_models::channels::MessageContent; use robespierre_models::events::ReadyEvent; // --snip-- #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing_subscriber::fmt::init(); let token = std::env::var("TOKEN") .expect("Cannot get token; set environment variable TOKEN=... and run again"); let auth = Authentication::bot(token); let http = Http::new(&auth).await?; let connection = Connection::connect(&auth).await?; let context = Context::new(http, robespierre::typemap::ShareMap::custom()).with_cache(CacheConfig::default()); let handler = Handler; let handler = CacheWrap::new(EventHandlerWrap::new(handler)); connection.run(context, handler).await?; Ok(()) } #[derive(Clone)] struct Handler; #[robespierre::async_trait] impl robespierre::EventHandler for Handler { async fn on_ready(&self, ctx: Context, ready: ReadyEvent) { tracing::info!("We're ready!"); } async fn on_message(&self, ctx: Context, message: Message) { if matches!(&message.content, MessageContent::Content(s) if s == "ping") { let _ = message.reply(&ctx, "pong").await; } } }
Now, let's run it:
TOKEN=... cargo run
For the source code of this, see the ping-reply-pong example.