Skip to content

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

rust
let client = Client::new(token, intents, handler, is_sandbox)?;

The arguments:

  • token: Token — your Token::new(app_id, secret) (or Token::from_env()).
  • intents: Intents — see Intents.
  • handler: H where H: EventHandler — your handler value (consumed by value, stored in an Arc internally).
  • is_sandbox: booltrue selects the sandbox base URL, false production.

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].

rust
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 the open_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:

rust
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;
    });
}

Released under the MIT License.