Readme in different languages: EN ยท RU
maxoxide
๐ฆ An async Rust library for building bots on the Max messenger platform, inspired by teloxide.
Features
- โ Coverage of the published Max Bot REST API
- โ Long polling and optional Webhook server via axum
- โ
Forward-compatible typed events (
Update,Message,Callback, unknown update fallback) - โ
Dispatcherwith fluent handler registration, composable filters, startup hooks and scheduled tasks - โ
Inline keyboards (
callback,link,message,open_app,clipboard,request_contact,request_geo_location) - โ File uploads โ multipart, image photo-token payloads, correct token flow for video/audio, send helpers for image/video/audio/file
- โ Markdown / HTML message formatting
- โ
Webhook secret verification (
X-Max-Bot-Api-Secret) - โ Tokio async throughout
Quick start
[]
= "2.0.0"
= { = "1", = ["full"] }
# Enable the built-in axum webhook server:
# maxoxide = { version = "2.0.0", features = ["webhook"] }
use ;
use Update;
async
MAX_BOT_TOKEN=your_token
API methods
| Method | Description |
|---|---|
bot.get_me() |
Bot info |
bot.send_text_to_chat(chat_id, text) |
Send plain text to a dialog/group/channel by chat_id |
bot.send_text_to_user(user_id, text) |
Send plain text to a user by global MAX user_id |
bot.send_markdown_to_chat(chat_id, text) |
Send Markdown to a dialog/group/channel by chat_id |
bot.send_markdown_to_user(user_id, text) |
Send Markdown to a user by global MAX user_id |
bot.send_message_to_chat(chat_id, body) |
Send message with attachments / keyboard by chat_id |
bot.send_message_to_user(user_id, body) |
Send message with attachments / keyboard by global MAX user_id |
bot.send_message_to_chat_with_options(chat_id, body, options) |
Send with query options such as disable_link_preview |
bot.edit_message(mid, body) |
Edit a message |
bot.delete_message(mid) |
Delete a message |
bot.get_messages_by_ids(ids, โฆ) |
Get one or more messages by message IDs |
bot.get_video(video_token) |
Get video metadata and playback URLs |
bot.answer_callback(body) |
Answer an inline button press |
bot.get_chat(chat_id) |
Chat info |
bot.get_chats(โฆ) |
List all group chats |
bot.edit_chat(chat_id, body) |
Edit chat title / description |
bot.leave_chat(chat_id) |
Leave a chat |
bot.get_members(โฆ) |
List members |
bot.get_members_by_ids(chat_id, user_ids) |
Get selected members |
bot.add_members(โฆ) |
Add members |
bot.remove_member(โฆ) |
Remove a member |
bot.get_admins(chat_id) |
List admins |
bot.add_admins(chat_id, admins) |
Grant admin rights |
bot.remove_admin(chat_id, user_id) |
Revoke admin rights |
bot.pin_message(โฆ) |
Pin a message |
bot.unpin_message(โฆ) |
Unpin |
bot.send_sender_action(chat_id, action) |
Send a typed sender action |
bot.subscribe(body) |
Register a webhook |
bot.get_upload_url(type) |
Get upload URL |
bot.upload_file(type, path, name, mime) |
Full two-step file upload |
bot.upload_bytes(type, bytes, name, mime) |
Same, from bytes |
bot.send_image_to_chat(...) |
Upload and send an image |
bot.send_video_to_chat(...) |
Upload and send a video |
bot.send_audio_to_chat(...) |
Upload and send audio |
bot.send_file_to_chat(...) |
Upload and send a generic file |
bot.set_my_commands(commands) |
Experimental: public MAX API currently returns 404 for /me/commands |
User ID vs Chat ID
These two IDs are different and should not be used interchangeably:
user_idis the global MAX ID of a user.chat_idis the ID of a concrete dialog, group, or channel.- In a private chat,
message.sender.user_ididentifies the user, whilemessage.chat_id()identifies that specific dialog with the bot. - Use
send_text_to_chat(chat_id, ...)/send_message_to_chat(chat_id, ...)when you already know the dialog or group. - Use
send_text_to_user(user_id, ...)/send_message_to_user(user_id, ...)when you only know the user's global MAX ID.
Known MAX platform gaps
As of April 27, 2026, the crate can send these requests, but live behavior on the MAX side is still inconsistent:
Button::RequestContactis documented by MAX, but live tests received a contact attachment with emptycontact_idandvcf_phone. Sending the button works; receiving the user's phone number is not confirmed on the MAX side.Button::RequestGeoLocationdelivers a structuredAttachment::Locationwithlatitudeandlongitude; the client can render the same shared position as a Yandex Maps card.bot.send_sender_action(chat_id, SenderAction::TypingOn)returns success from the API, but live MAX tests did not confirm a visible typing indicator in the client.bot.set_my_commandsis kept as an experimental helper, but the public MAX REST docs do not list a write endpoint for bot commands, and livePOST /me/commandsrequests return404 Path /me/commands is not recognized.
Dispatcher filters
dp.on_command; // specific command
dp.on_message; // any new message
dp.on_edited_message; // edited message
dp.on_callback; // any callback button
dp.on_callback_payload; // specific payload
dp.on_bot_started; // user starts bot
dp.on_update; // composable filters
dp.on_start; // runs before polling starts
dp.task; // periodic task
dp.on_raw_update; // raw JSON for every update
dp.on_filter; // custom predicate
dp.on; // every update
First matching handler wins. Register more specific filters before general ones.
Inline keyboard
use ;
let keyboard = KeyboardPayload ;
let body = text.with_keyboard;
bot.send_message_to_chat.await?;
Clipboard buttons copy their payload in the MAX client. They do not send a
callback update to the bot:
let keyboard = KeyboardPayload ;
let body = empty.with_keyboard;
bot.send_message_to_chat.await?;
File upload
Max uses a two-step upload flow. upload_file / upload_bytes handle it automatically:
use ;
let token = bot
.upload_file
.await?;
let body = NewMessageBody ;
bot.send_message_to_chat.await?;
// or:
// bot.send_message_to_user(user_id, body).await?;
Note:
type=photowas removed from the Max API. Always useUploadType::Image.
For common cases, use upload-and-send helpers:
bot.send_image_to_chat.await?;
bot.send_video_to_chat.await?;
bot.send_audio_to_chat.await?;
bot.send_file_to_chat.await?;
There are matching *_to_user and *_bytes_* helpers.
MAX can take a short time to finish processing uploaded attachments after the upload request succeeds. The upload-and-send helpers retry briefly when the API reports that an attachment is not processed yet.
Image uploads can return a MAX photos token map instead of a single token. The send_image_* helpers preserve that payload automatically.
Webhook server (features = ["webhook"])
use WebhookServer;
use SubscribeBody;
bot.subscribe.await?;
new
.secret
.path
.serve
.await;
Max requires HTTPS on port 443 and does not support self-signed certificates.
Use webhooks when your bot has a public HTTPS endpoint and you want MAX to deliver updates via incoming requests. For local development and simple launches, long polling is usually enough.
Project layout
maxoxide/
โโโ Cargo.toml
โโโ src/
โ โโโ lib.rs โ public API & re-exports
โ โโโ bot.rs โ Bot + all HTTP methods
โ โโโ uploader.rs โ two-step file upload helpers
โ โโโ dispatcher.rs โ Dispatcher, Filter, Context
โ โโโ errors.rs โ MaxError
โ โโโ webhook.rs โ axum webhook server (feature = "webhook")
โ โโโ tests.rs โ unit tests
โ โโโ types.rs โ all types (User, Chat, Message, Update, โฆ)
โโโ examples/
โโโ echo_bot.rs
โโโ dispatcher_filters_bot.rs
โโโ keyboard_bot.rs
โโโ media_bot.rs
โโโ live_api_test.rs
โโโ webhook_bot.rs (feature = "webhook")
Running tests
Live API test
For real-data verification there is a separate interactive harness:
At startup it asks in the terminal for:
- bot token
- bot URL for the tester
- optional webhook URL and secret
- optional local file path for
upload_file - optional image, video and audio paths for media helper checks
- HTTP timeout, polling timeout and delay between requests
The harness then walks the tester through Max-client actions and records PASS / FAIL / SKIP for real API calls. It uses small delays between requests, drains the long-poll backlog before the run, and asks for explicit confirmation before destructive or non-reversible steps such as:
set_my_commandsdelete_chatleave_chat- visible group title edits
The current run also probes the unclear MAX behavior around contact/location request buttons, message buttons, open_app, clipboard, sender actions, uploaded video metadata, selected members, and temporary admin rights changes.