Client and event handler
Client and EventHandler are the two pieces every bot is built around. Client owns the gateway connection and the HTTP layer; EventHandler is the trait you implement to react to events the gateway delivers.
Constructing the client
let client = Client::new(token, intents, handler, is_sandbox)?;The arguments:
token: Token— yourToken::new(app_id, secret)(orToken::from_env()).intents: Intents— see Intents.handler: H where H: EventHandler— your handler value (consumed by value, stored in anArcinternally).is_sandbox: bool—trueselects the sandbox base URL,falseproduction.
Client::with_config(token, intents, handler, timeout_secs, is_sandbox) is the same constructor with an extra HTTP timeout in seconds; the default is botrs::DEFAULT_TIMEOUT (30 s).
After construction, call client.start().await. The future resolves only after the gateway connection ends and the event channel drains. Internally start validates the token, fetches /users/@me and the gateway URL, spawns a session manager that runs each shard, and pumps events back into the event handler.
The handler trait
EventHandler has one async fn per dispatched gateway event. Every method has a default empty implementation, so you only override the events you care about. Annotate the impl with #[async_trait::async_trait].
struct MyBot;
#[async_trait::async_trait]
impl EventHandler for MyBot {
async fn ready(&self, session: ReadySession) {
tracing::info!("ready as {}", session.event().user.username);
}
async fn message_create(&self, mut session: ChannelReplySession) {
let message = session.message().clone();
if message.author.bot {
return;
}
if message.content.trim() == "!ping" {
let _ = session.reply("pong").await;
}
}
}Available callbacks
The complete set of callbacks receives one session parameter per event:
- Lifecycle:
ready,resumed,error(BotError),unknown_event(UnknownEventSession). - Messages:
message_create,message_delete,direct_message_create,direct_message_delete,group_message_create,c2c_message_create. - Reactions:
message_reaction_add,message_reaction_remove. - Interactions:
interaction_create. - Audit:
message_audit_pass,message_audit_reject. - Guilds and channels:
guild_create,guild_update,guild_delete,channel_create,channel_update,channel_delete. - Members:
guild_member_add,guild_member_update,guild_member_remove. - Audio:
audio_start,audio_finish,on_mic,off_mic,audio_or_live_channel_member_enter,audio_or_live_channel_member_exit. - Forum:
forum_thread_create/_update/_delete,forum_post_create/_delete,forum_reply_create/_delete,forum_publish_audit_result, plus theopen_forum_*mirrors. - C2C and group management:
friend_add,friend_del,c2c_msg_reject,c2c_msg_receive,subscribe_message_status,enter_aio,group_add_robot,group_del_robot,group_msg_reject,group_msg_receive.
A callback only fires if the matching Intents flag is enabled — see the Intents page for the mapping.
Session Parameters
Every callback receives a session object. Message create callbacks use reply sessions such as ChannelReplySession, GroupReplySession, C2CReplySession, and DirectReplySession. Other callbacks use event sessions such as ReadySession, MemberSession, or MessageDeleteSession.
Use session.event() to inspect generic event payloads, and use session.message() for reply sessions. Every session exposes the shared REST client through session.api(), session.api_handle(), and Deref<Target = BotApi>.
The error callback
If dispatch setup fails before your handler is called, the framework calls EventHandler::error(&self, error: BotError) once and continues processing the next event. Payload parse failures are logged and dropped, and handler panics are not converted into BotError. Override error if you want custom metrics or alerts, but retries must come from your own code.
Spawning work from a handler
A handler call should return promptly so the next event can be dispatched. For long-running work, move an owned API handle into tokio::spawn:
async fn message_create(&self, session: ChannelReplySession) {
let api = session.api_handle();
tokio::spawn(async move {
let params = MessageParams::new_text("done");
let _ = api.send_message("channel", params).await;
});
}