Your First Bot
This guide walks through building a complete echo bot step by step. By the end, you will understand the fundamental pattern used by every bot built with this framework.
The Echo Bot
An echo bot does one thing: it repeats back whatever text the user sends. Despite its simplicity, it demonstrates three core concepts:
- Command handlers – respond to
/startand/help. - Message handlers with filters – catch text messages that are not commands.
- The Application lifecycle – build, register handlers, and run.
Full Source
use rust_tg_bot::ext::prelude::{
ApplicationBuilder, Arc, CommandHandler, Context, HandlerResult,
MessageHandler, Update, COMMAND, TEXT,
};
async fn start(update: Arc<Update>, context: Context) -> HandlerResult {
let name = update
.effective_user()
.map(|u| u.first_name.as_str())
.unwrap_or("there");
context
.reply_text(
&update,
&format!(
"Hi {name}! I am an echo bot. Send me any message and \
I will repeat it back to you.\n\nUse /help to see \
available commands."
),
)
.await?;
Ok(())
}
async fn help(update: Arc<Update>, context: Context) -> HandlerResult {
context
.reply_text(
&update,
"Available commands:\n\
/start - Start the bot\n\
/help - Show this help message\n\n\
Send any text message and I will echo it back!",
)
.await?;
Ok(())
}
async fn echo(update: Arc<Update>, context: Context) -> HandlerResult {
let text = update
.effective_message()
.and_then(|m| m.text.as_deref())
.unwrap_or("");
if !text.is_empty() {
context.reply_text(&update, text).await?;
}
Ok(())
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let token = std::env::var("TELEGRAM_BOT_TOKEN")
.expect("TELEGRAM_BOT_TOKEN environment variable must be set");
let app = ApplicationBuilder::new().token(token).build();
app.add_handler(CommandHandler::new("start", start), 0).await;
app.add_handler(CommandHandler::new("help", help), 0).await;
app.add_handler(
MessageHandler::new(TEXT() & !COMMAND(), echo), 0,
).await;
println!("Echo bot is running. Press Ctrl+C to stop.");
if let Err(e) = app.run_polling().await {
eprintln!("Error running bot: {e}");
}
}
Line-by-Line Walkthrough
Imports
#![allow(unused)]
fn main() {
use rust_tg_bot::ext::prelude::{
ApplicationBuilder, Arc, CommandHandler, Context, HandlerResult,
MessageHandler, Update, COMMAND, TEXT,
};
}
The prelude module re-exports everything you need for common bot development. Always import specific items – avoid wildcard imports for clarity and faster compilation.
Handler Signature
Every handler function follows this signature:
#![allow(unused)]
fn main() {
async fn handler_name(update: Arc<Update>, context: Context) -> HandlerResult {
// ...
Ok(())
}
}
update: Arc<Update>– the incoming Telegram update, wrapped inArcfor cheap cloning across async tasks.context: Context– provides access to the bot instance, user/chat data, job queue, and convenience methods likereply_text.HandlerResult– an alias forResult<(), HandlerError>.
Extracting Data from Updates
#![allow(unused)]
fn main() {
let name = update
.effective_user()
.map(|u| u.first_name.as_str())
.unwrap_or("there");
}
The Update type provides typed accessor methods:
effective_user()– the user who triggered the update.effective_chat()– the chat the update originated from.effective_message()– the message associated with the update.callback_query()– the callback query, if any.
These return Option types, so you use standard Rust patterns to handle the None case.
Replying to Messages
#![allow(unused)]
fn main() {
context.reply_text(&update, "Hello!").await?;
}
context.reply_text() is a convenience method that:
- Extracts the chat ID from the update.
- Calls
bot.send_message(chat_id, text). - Returns a
Resultyou can propagate with?.
For more control, use the bot directly:
#![allow(unused)]
fn main() {
context.bot().send_message(chat_id, "Hello!")
.parse_mode(ParseMode::Html)
.await?;
}
Building the Application
#![allow(unused)]
fn main() {
let app = ApplicationBuilder::new().token(token).build();
}
ApplicationBuilder uses a typestate pattern: you cannot call .build() until you have called .token(). This is enforced at compile time.
Registering Handlers
#![allow(unused)]
fn main() {
app.add_handler(CommandHandler::new("start", start), 0).await;
app.add_handler(MessageHandler::new(TEXT() & !COMMAND(), echo), 0).await;
}
CommandHandler::new("start", start)– matches/startand calls thestartfunction.MessageHandler::new(TEXT() & !COMMAND(), echo)– matches text messages that are NOT commands, then callsecho.- The second argument (
0) is the handler group. Groups are processed in ascending order. Within a group, the first matching handler wins.
Running
#![allow(unused)]
fn main() {
app.run_polling().await.unwrap();
}
run_polling() starts the bot in long-polling mode: it repeatedly calls Telegram’s getUpdates endpoint and dispatches incoming updates to your handlers.
Next Steps
You now understand the fundamental pattern. Every bot you build follows the same structure:
- Define async handler functions.
- Build an
ApplicationwithApplicationBuilder. - Register handlers.
- Call
run_polling()orrun_webhook().
Continue to Running Your Bot to learn about environment configuration and the different ways to receive updates.