4 releases
| 0.1.3 | Sep 29, 2025 |
|---|---|
| 0.1.2 | Sep 25, 2025 |
| 0.1.1 | Sep 25, 2025 |
| 0.1.0 | Sep 25, 2025 |
#6 in #smart
117 downloads per month
24KB
132 lines
🚀 Teloxide Plugins
The easiest way to create Telegram bots with Rust!
Smart plugin system for Teloxide bots - write less code, do more!
✨ What is Teloxide Plugins?
Teloxide Plugins revolutionizes Telegram bot development in Rust by providing a powerful yet simple plugin system. Instead of writing complex message handling and routing code, you just add a #[TeloxidePlugin] attribute above your functions, and they automatically become bot commands!
Before (Traditional Approach):
let handler = dptree::entry()
.branch(Update::filter_message()
.branch(dptree::filter(|msg: Message| msg.text() == Some("/ping".to_string()))
.endpoint(ping_handler))
.branch(dptree::filter(|msg: Message| msg.text() == Some("/help".to_string()))
.endpoint(help_handler)));
After (With Teloxide Plugins):
#[TeloxidePlugin(commands = ["ping"], prefixes = ["/"])]
async fn ping(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "pong!").await.unwrap();
}
That's it! 🎉
📋 Prerequisites
- Rust 1.70+ - Install Rust
- Telegram Bot Token - Get one from @BotFather
- Basic Rust Knowledge - Understanding of
async/await
🚀 Quick Start
1. Create Your Bot Project
cargo new my-awesome-bot
cd my-awesome-bot
2. Add Dependencies
Add to your Cargo.toml:
[dependencies]
teloxide = "0.17"
teloxide-plugins = "0.1.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
3. Create Your First Plugin
Replace src/main.rs with:
use teloxide::prelude::*;
use teloxide_plugins::{PluginContext, dispatch, TeloxidePlugin};
#[TeloxidePlugin(commands = ["ping", "p"], prefixes = ["/", "!"])]
async fn ping_handler(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "🏓 Pong!").await.unwrap();
}
#[TeloxidePlugin(regex = ["(?i)hello"])]
async fn greeting_handler(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "👋 Hi there! How can I help you?").await.unwrap();
}
#[TeloxidePlugin(commands = ["help"], prefixes = ["/"])]
async fn help_handler(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "🤖 Available commands: /ping, /help").await.unwrap();
}
async fn message_handler(bot: Bot, msg: Message) -> ResponseResult<()> {
let ctx = PluginContext::new(bot.clone(), Some(msg.clone()), None);
dispatch(ctx).await?;
Ok(())
}
#[tokio::main]
async fn main() {
let bot = Bot::new("YOUR_BOT_TOKEN");
let handler = dptree::entry()
.branch(Update::filter_message().endpoint(message_handler));
println!("🚀 Bot is starting... Send /ping to test!");
Dispatcher::builder(bot, handler)
.enable_ctrlc_handler()
.build()
.dispatch()
.await;
}
4. Get Your Bot Token
- Message @BotFather on Telegram
- Send
/newbotand follow the instructions - Copy your bot token
- Replace
"YOUR_BOT_TOKEN"in the code
5. Run Your Bot
cargo run
Test it by sending /ping or hello to your bot! 🎉
📖 Table of Contents
- ✨ What is Teloxide Plugins?
- 📋 Prerequisites
- 🚀 Quick Start
- 🎯 Plugin Types
- 🔧 Advanced Usage
- 📚 Examples
- 🛠️ API Reference
- 🔍 Troubleshooting
- 🤝 Contributing
- 📄 License
🎯 Plugin Types
Command Plugins 📝
Respond to specific commands like /start, /help, etc.
#[TeloxidePlugin(commands = ["start", "help"], prefixes = ["/", "!"])]
async fn help_command(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "Welcome! Use /ping to test me!").await.unwrap();
}
What this does:
- Responds to
/startOR/help - Also responds to
!startOR!help - Sends a welcome message
Regex Plugins 🔍
Respond to messages that match a pattern.
#[TeloxidePlugin(regex = ["(?i)good morning"])]
async fn morning_greeting(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "Good morning! ☀️").await.unwrap();
}
What this does:
(?i)means case-insensitive- Responds to "good morning", "Good Morning", "GOOD MORNING", etc.
Callback Plugins 🔘
Handle button clicks and inline keyboard interactions.
#[TeloxidePlugin(commands = ["menu"], prefixes = ["/"])]
async fn show_menu(bot: Bot, msg: Message) {
let button = InlineKeyboardButton::new(
"Click me!",
InlineKeyboardButtonKind::CallbackData("button_clicked".to_string())
);
let keyboard = InlineKeyboardMarkup::new(vec![vec![button]]);
bot.send_message(msg.chat.id, "Choose an option:")
.reply_markup(keyboard)
.await
.unwrap();
}
#[TeloxidePlugin(callback = ["button_clicked"])]
async fn handle_button_click(bot: Bot, cq: CallbackQuery) {
if let Some(message) = cq.message {
bot.send_message(message.chat().id, "Button was clicked! 🎉").await.unwrap();
bot.answer_callback_query(cq.id).await.unwrap();
}
}
🔧 Advanced Usage
Multiple Commands & Prefixes
One plugin can handle multiple commands with different prefixes:
#[TeloxidePlugin(commands = ["start", "help", "h"], prefixes = ["/", "!", "."])]
async fn universal_help(bot: Bot, msg: Message) {
bot.send_message(
msg.chat.id,
"🤖 Available: /start, /help, !start, !help, .start, .help"
).await.unwrap();
}
Error Handling
Handle errors gracefully in your plugins:
#[TeloxidePlugin(commands = ["error_test"], prefixes = ["/"])]
async fn error_example(bot: Bot, msg: Message) {
match bot.send_message(msg.chat.id, "This might fail!").await {
Ok(_) => {},
Err(e) => {
eprintln!("Failed to send message: {:?}", e);
let _ = bot.send_message(msg.chat.id, "❌ Sorry, something went wrong!").await;
}
}
}
State Management
For stateful bots, you can use static variables or dependency injection:
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
#[TeloxidePlugin(commands = ["count"], prefixes = ["/"])]
async fn counter_bot(bot: Bot, msg: Message) {
let count = COUNTER.fetch_add(1, Ordering::SeqCst);
bot.send_message(msg.chat.id, format!("Count: {}", count + 1))
.await.unwrap();
}
📚 Examples
Echo Bot
#[TeloxidePlugin(commands = ["echo"], prefixes = ["/"])]
async fn echo_bot(bot: Bot, msg: Message) {
if let Some(text) = msg.text() {
bot.send_message(msg.chat.id, text).await.unwrap();
}
}
Weather Bot (External API)
use serde::Deserialize;
#[derive(Deserialize)]
struct WeatherResponse {
weather: Vec<WeatherInfo>,
main: MainInfo,
}
#[derive(Deserialize)]
struct WeatherInfo {
main: String,
description: String,
}
#[derive(Deserialize)]
struct MainInfo {
temp: f64,
humidity: u32,
}
#[TeloxidePlugin(commands = ["weather"], prefixes = ["/"])]
async fn weather_bot(bot: Bot, msg: Message) {
if let Some(city) = msg.text().unwrap().strip_prefix("/weather ") {
match fetch_weather(city).await {
Ok(weather) => {
let response = format!(
"🌤️ Weather in {}: {} ({}°C, {}% humidity)",
city, weather.weather[0].description,
weather.main.temp, weather.main.humidity
);
bot.send_message(msg.chat.id, response).await.unwrap();
}
Err(_) => {
bot.send_message(msg.chat.id, "❌ Failed to fetch weather").await.unwrap();
}
}
}
}
Interactive Menu Bot
#[TeloxidePlugin(commands = ["menu"], prefixes = ["/"])]
async fn show_menu(bot: Bot, msg: Message) {
let keyboard = InlineKeyboardMarkup::new(vec![
vec![
InlineKeyboardButton::new("Option 1", InlineKeyboardButtonKind::CallbackData("opt1".to_string())),
InlineKeyboardButton::new("Option 2", InlineKeyboardButtonKind::CallbackData("opt2".to_string())),
],
vec![
InlineKeyboardButton::new("Option 3", InlineKeyboardButtonKind::CallbackData("opt3".to_string())),
]
]);
bot.send_message(msg.chat.id, "Choose an option:")
.reply_markup(keyboard)
.await.unwrap();
}
#[TeloxidePlugin(callback = ["opt1", "opt2", "opt3"])]
async fn handle_menu_selection(bot: Bot, cq: CallbackQuery) {
if let Some(data) = &cq.data {
let response = match data.as_str() {
"opt1" => "You selected Option 1! 🎯",
"opt2" => "You selected Option 2! 🎯",
"opt3" => "You selected Option 3! 🎯",
_ => "Unknown option"
};
if let Some(message) = cq.message {
bot.send_message(message.chat().id, response).await.unwrap();
}
bot.answer_callback_query(cq.id).await.unwrap();
}
}
🛠️ API Reference
#[TeloxidePlugin] Attributes
| Attribute | Type | Description | Example |
|---|---|---|---|
commands |
Vec<&str> |
Command names to respond to | ["ping", "start"] |
prefixes |
Vec<&str> |
Command prefixes | ["/", "!"] |
regex |
&str |
Regex pattern for matching | "(?i)hello" |
callback |
&str |
Callback data filter | "button_clicked" |
Core Types
PluginContext: Contains bot instance, message, and callback querydispatch(): Main function that routes messages to appropriate pluginsPluginMeta: Metadata structure for plugin registration
⚡ Performance & Architecture
Runtime-Free Plugin Registration
Plugins are registered during static initialization, before the tokio runtime starts:
#[ctor::ctor]
fn plugin_constructor() {
register_plugin(&plugin_metadata);
}
Zero-Blocking Runtime
Once the bot is running, all operations are fully async:
- ✅ Plugin registration: Static initialization (microseconds)
- ✅ Message dispatch: Fully async with
awaitpoints - ✅ Plugin execution: Each plugin runs independently async
- ✅ Regex compilation: Cached with concurrent read access
Production Benefits
- Startup: ~1 microsecond per plugin registration
- Runtime: Zero blocking operations, unlimited concurrent message processing
- Scalability: Handles thousands of concurrent messages efficiently
- Reliability: No runtime dependency during initialization
🔍 Troubleshooting
Common Issues
Bot Doesn't Respond
- ✅ Check bot token: Verify your token is correct and active
- ✅ Check permissions: Ensure bot can send messages in the chat
- ✅ Verify plugin syntax: Make sure
#[TeloxidePlugin]attributes are correct - ✅ Check bot is running: Run
cargo runand look for startup messages
Compilation Errors
- ✅ Update dependencies: Ensure all versions in
Cargo.tomlare compatible - ✅ Check Rust version: Requires Rust 1.70+
- ✅ Import statements: Verify all necessary imports are present
Regex Not Matching
- ✅ Test pattern: Use online regex testers to validate your patterns
- ✅ Case sensitivity: Use
(?i)flag for case-insensitive matching - ✅ Anchors: Add
^and$for exact matches
Performance Issues
- ✅ Regex compilation: Patterns are cached automatically
- ✅ Plugin count: Too many plugins can slow down dispatch
- ✅ Message frequency: Consider rate limiting for high-traffic bots
Debug Mode
Enable debug logging to see what's happening:
#[TeloxidePlugin(commands = ["debug"], prefixes = ["/"])]
async fn debug_handler(bot: Bot, msg: Message) {
println!("Debug - Message: {:?}", msg);
println!("Debug - Text: {:?}", msg.text());
println!("Debug - Chat ID: {:?}", msg.chat.id);
bot.send_message(msg.chat.id, "Debug info logged to console").await.unwrap();
}
🤝 Contributing
We welcome contributions! Here's how to get involved:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
git clone https://github.com/Junaid433/teloxide-plugins.git
cd teloxide-plugins
cargo test
cargo run --example bot
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
Happy bot building! 🤖✨
Made with ❤️ for the Rust community
Dependencies
~16–33MB
~482K SLoC