Skip to content

Migration from a Python QQ bot SDK

This page maps the most common Python QQ-bot SDK patterns to their botrs equivalents. The framework's surface area is intentionally similar — the same gateway, the same intents, the same message types — so most of the work is mechanical.

Handler class → EventHandler trait

In a Python SDK you typically subclass a Client or register message callbacks via decorators.

In botrs you implement the EventHandler trait on a struct of your own. Each event is one async fn with a default empty body, so you only override what you care about. The handler must be Send + Sync and 'static.

rust
use botrs::{ChannelReplySession, Client, EventHandler, Intents, Token};

struct MyBot;

#[async_trait::async_trait]
impl EventHandler for MyBot {
    async fn message_create(&self, mut session: ChannelReplySession) {
        let message = session.message().clone();
        if message.content.contains("ping") {
            let _ = session.reply("pong").await;
        }
    }
}

BotRS callbacks are session-first: the callback receives one session value that contains the inbound message or event payload and exposes the shared BotApi.

Token

Python SDKs accept an app_id and a secret (sometimes called token). In botrs:

rust
let token = Token::new(app_id, secret);
// or read QQ_BOT_APP_ID + QQ_BOT_SECRET from the environment
let token = Token::from_env()?;

Token::validate() exists for sanity-checking before you start; it errors with BotError::Auth on missing fields.

Intents

Where Python uses something like Intents.default() | Intents.public_guild_messages, botrs uses named constructors and chainable flag builders:

rust
let intents = Intents::new()
    .with_public_guild_messages()
    .with_direct_message();

Intents::new() gives you an exact set made only of the flags listed above. Intents::default() is also available as the public, non-privileged preset; ENTER_AIO remains opt-in. The full table is in intents.

Sending a message

Python keyword-argument message calls translate to the *Params builder family:

rust
use botrs::models::message::MessageParams;

let params = MessageParams::new_text("hi").with_reply(&msg_id);
session.send_message(params).await?;

The same pattern applies to group, C2C, and DM messages: GroupMessageParamssend_group_message, C2CMessageParamssend_c2c_message, DirectMessageParamssend_direct_message.

In event handlers, session.reply, send_markdown_message, send_embed_message, send_ark_message, send_keyboard_message, and group/C2C send_media_message cover the common cases. For direct BotApi calls or custom fields, use params constructors such as new_markdown, new_embed, new_ark, new_keyboard, and new_media.

Starting the bot

Python: client.run(app_id, secret).

botrs:

rust
let mut client = Client::new(token, intents, MyBot, false /* is_sandbox */)?;
client.start().await?;

client.start().await is the long-lived future. Pair it with tokio::main (or your own runtime).

Logging

tracing replaces Python's logging. The framework emits at info for lifecycle events and debug for gateway frames. Initialize a subscriber once:

rust
tracing_subscriber::fmt().with_env_filter("botrs=info").init();

Error handling

Python's try/except becomes Rust's match / ?. Every API call returns Result<T, BotError>. See error handling for the variant list. The framework does not retry on your behalf, so if your Python code relied on the SDK retrying transient failures, port that loop over manually.

Released under the MIT License.