Fluent API¶
Every bot.method(required_args) returns a builder that implements IntoFuture.
Chain optional params before .await — no None, no Some(...).
Sending messages¶
// just required args
bot.send_message(chat_id, "Hello!").await?;
// with options chained
bot.send_message(chat_id, "<b>Bold text</b>")
.html()
.silent()
.reply_to(msg.message_id)
.await?;
// all options available by field name
bot.send_message(chat_id, "hi")
.parse_mode("MarkdownV2")
.disable_notification(true)
.await?;
Sending media¶
use ferobot::InputFile;
// File ID already on Telegram
bot.send_photo(chat_id, "AgACAgIAAxkBAAI...").await?;
// URL: Telegram fetches it
bot.send_photo(chat_id, "https://example.com/img.jpg").await?;
// Raw bytes from disk
let data = tokio::fs::read("photo.jpg").await?;
bot.send_photo(chat_id, InputFile::memory("photo.jpg", data))
.caption("Look at this!")
.await?;
Inline keyboard¶
use ferobot::ReplyMarkup;
use ferobot::types::{InlineKeyboardButton, InlineKeyboardMarkup};
let kb = ReplyMarkup::InlineKeyboard(InlineKeyboardMarkup {
inline_keyboard: vec![vec![
InlineKeyboardButton {
text: "Yes".into(),
callback_data: Some("yes".into()),
..Default::default()
},
InlineKeyboardButton {
text: "No".into(),
callback_data: Some("no".into()),
..Default::default()
},
]],
});
bot.send_message(chat_id, "Confirm?")
.reply_markup(kb)
.await?;
Answering callbacks¶
bot.answer_callback_query(&cq.id)
.text("Done!")
.show_alert(false)
.await?;
Editing messages¶
use ferobot::types::MaybeInaccessibleMessage;
if let Some(MaybeInaccessibleMessage::Message(m)) = cq.message.as_deref() {
bot.edit_message_text("Updated text")
.chat_id(m.chat.id)
.message_id(m.message_id)
.await?;
}
Message helpers¶
// Reply to a message
msg.reply(&bot, "Thanks!").await?;
// Get text (also reads caption for media)
if let Some(text) = msg.get_text() { /* ... */ }
// Get a t.me deep link to the message
let link = msg.get_link(); // "https://t.me/c/1234/56"
// Get sender
let user = msg.get_sender(); // Option<&User>
// File URL from a File struct
let url = file.url(&bot); // Option<String>
InputFile¶
// File already on Telegram: pass file_id string directly
bot.send_photo(chat_id, "AgACAgIAAxkBAAI...").await?;
// URL: pass URL string directly
bot.send_photo(chat_id, "https://example.com/img.jpg").await?;
// Raw bytes
InputFile::memory("photo.jpg", bytes)
Raw API access¶
Call any Bot API method by name with arbitrary params:
let result: serde_json::Value = bot
.raw("sendMessage")
.param("chat_id", 123456789_i64)
.param("text", "Hello from raw!")
.param("parse_mode", "HTML")
.call()
.await?;
Sending files (full reference)¶
Input type |
How to pass it |
|---|---|
Existing |
Pass the ID string directly |
HTTP URL |
Pass the URL string directly (Telegram fetches) |
Bytes / disk file |
|
let bytes = tokio::fs::read("document.pdf").await?;
bot.send_document(chat_id, InputFile::memory("doc.pdf", bytes))
.await?;
Webhook (built-in vs manual)¶
Feature |
Built-in |
Manual (bring your own) |
|---|---|---|
Zero boilerplate |
yes |
no |
Secret token validation |
Built-in |
Manual |
Custom routing / middleware |
no |
yes |
Works with existing server |
no |
yes |
Feature flag needed |
|
none |
Manual webhook with Axum:
use axum::{extract::State, http::StatusCode, routing::post, Json, Router};
use std::sync::Arc;
use ferobot::{types::Update, Bot};
struct AppState { bot: Bot }
#[tokio::main]
async fn main() {
let bot = Bot::new("YOUR_TOKEN").await.unwrap();
bot.set_webhook("https://yourdomain.com/bot").await.unwrap();
let app = Router::new()
.route("/bot", post(handle))
.with_state(Arc::new(AppState { bot }));
axum::serve(
tokio::net::TcpListener::bind("0.0.0.0:8443").await.unwrap(),
app,
).await.unwrap();
}
async fn handle(
State(s): State<Arc<AppState>>,
Json(update): Json<Update>,
) -> StatusCode {
let bot = s.bot.clone();
tokio::spawn(async move {
if let Some(msg) = update.message {
let _ = bot.send_message(msg.chat.id, "got it").await;
}
});
StatusCode::OK
}