| Crates.io | teloxide-plugins |
| lib.rs | teloxide-plugins |
| version | 0.1.4 |
| created_at | 2025-09-25 03:16:43.910375+00 |
| updated_at | 2025-11-17 10:37:24.497564+00 |
| description | Smart plugin system for Teloxide bots |
| homepage | |
| repository | https://github.com/Junaid433/Teloxide-Plugins |
| max_upload_size | |
| id | 1854103 |
| size | 72,129 |
The easiest way to create Telegram bots with Rust!
Smart plugin system for Teloxide bots - write less code, do more!
It's a macro-based plugin system for teloxide. Instead of manually wiring up dispatch trees, you slap a #[TeloxidePlugin] attribute on your functions and the macro handles the routing.
The old way:
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)));
The new way:
#[TeloxidePlugin(commands = ["ping"], prefixes = ["/"])]
async fn ping(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "pong!").await.unwrap();
}
That's the basic idea.
cargo new my-bot
cd my-bot
Your Cargo.toml should look something like:
[dependencies]
teloxide = "0.17"
teloxide-plugins = "0.1.4"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
Replace src/main.rs with this:
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, "Hey!").await.unwrap();
}
#[TeloxidePlugin(commands = ["help"], prefixes = ["/"])]
async fn help_handler(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "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_HERE");
let handler = dptree::entry()
.branch(Update::filter_message().endpoint(message_handler));
println!("Bot is running... try sending /ping");
Dispatcher::builder(bot, handler)
.enable_ctrlc_handler()
.build()
.dispatch()
.await;
}
cargo run
Then message your bot with /ping or just "hello".
For commands like /start or /help:
#[TeloxidePlugin(commands = ["start", "help"], prefixes = ["/", "!"])]
async fn help_command(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "Welcome!").await.unwrap();
}
You can pass multiple commands and prefixes. The handler will respond to any combination.
For pattern matching:
#[TeloxidePlugin(regex = ["(?i)good morning"])]
async fn morning_greeting(bot: Bot, msg: Message) {
bot.send_message(msg.chat.id, "Morning! ☕").await.unwrap();
}
The (?i) flag makes it case-insensitive. You can use full regex features here, but keep in mind it'll run on every message, so don't go too crazy with complex patterns.
For handling inline button clicks:
#[TeloxidePlugin(commands = ["menu"], prefixes = ["/"])]
async fn show_menu(bot: Bot, msg: Message) {
let button = InlineKeyboardButton::new(
"Click me",
InlineKeyboardButtonKind::CallbackData("btn_click".to_string())
);
let keyboard = InlineKeyboardMarkup::new(vec![vec![button]]);
bot.send_message(msg.chat.id, "Pick something:")
.reply_markup(keyboard)
.await
.unwrap();
}
#[TeloxidePlugin(callback = ["btn_click"])]
async fn handle_click(bot: Bot, cq: CallbackQuery) {
if let Some(msg) = cq.message {
bot.send_message(msg.chat().id, "You clicked it!").await.unwrap();
bot.answer_callback_query(cq.id).await.unwrap();
}
}
The macro doesn't magically handle errors for you. You'll still need to deal with them in your handlers:
#[TeloxidePlugin(commands = ["might_fail"], prefixes = ["/"])]
async fn error_example(bot: Bot, msg: Message) {
match bot.send_message(msg.chat.id, "Trying...").await {
Ok(_) => {},
Err(e) => {
eprintln!("Send failed: {}", e);
}
}
}
There's no built-in state management yet. For simple counters, you can use statics:
use std::sync::atomic::{AtomicU32, Ordering};
static COUNT: AtomicU32 = AtomicU32::new(0);
#[TeloxidePlugin(commands = ["count"], prefixes = ["/"])]
async fn counter(bot: Bot, msg: Message) {
let current = COUNT.fetch_add(1, Ordering::SeqCst);
bot.send_message(msg.chat.id, format!("Count: {}", current + 1))
.await.unwrap();
}
For anything more complex, you'll need to roll your own solution or wait for a future version.
Plugin registration happens at startup, not runtime. The regex patterns are compiled once and cached. For bots handling tons of messages, the dispatch overhead is minimal - it's basically a hashmap lookup and a regex match against cached patterns.
If you have hundreds of plugins with complex regexes, yeah, maybe reconsider your bot design.
Check the examples/ directory in the repo for full working bots:
echo.rs - Simple echo functionalityweather.rs - Hitting an external API (needs an API key)menu.rs - Interactive menus with callbacksThe weather example looks like this in practice:
#[TeloxidePlugin(commands = ["weather"], prefixes = ["/"])]
async fn weather(bot: Bot, msg: Message) {
if let Some(text) = msg.text() {
if let Some(city) = text.strip_prefix("/weather ") {
match get_weather_for_city(city).await {
Ok(w) => {
bot.send_message(msg.chat.id, format!("It's {}°C in {}", w.temp, city))
.await.unwrap();
}
Err(_) => {
bot.send_message(msg.chat.id, "Couldn't fetch weather")
.await.unwrap();
}
}
}
}
}
You'll need to implement get_weather_for_city() yourself - this crate doesn't include HTTP clients.
The #[TeloxidePlugin] macro accepts these attributes:
| Attribute | What it does | Example |
|---|---|---|
commands |
List of command names | ["ping", "start"] |
prefixes |
Command prefixes | ["/", "!"] |
regex |
Regex patterns to match | ["(?i)hi"] |
callback |
Callback data strings | ["btn1"] |
You can combine multiple attributes on the same function, though it's usually cleaner to keep them separate.
Bot doesn't respond?
dispatch() in your message handlerRUST_LOG=debug to see what's happeningCompilation errors?
teloxide 0.17+. Older versions won't work.use teloxide_plugins::TeloxidePlugin;Regex not matching?
Test your pattern on regex101.com first. Remember that (?i) is your friend for case-insensitive matches.
This is a side project, so PRs are welcome but might take a bit to review. Some areas that need work:
To get started:
git clone https://github.com/Junaid433/teloxide-plugins.git
cd teloxide-plugins
cargo test
MIT - see LICENSE file for details.
Built because I got tired of copy-pasting dispatch trees