Crates.io | teloxide-plugins |
lib.rs | teloxide-plugins |
version | 0.1.2 |
created_at | 2025-09-25 03:16:43.910375+00 |
updated_at | 2025-09-25 21:51:01.205068+00 |
description | Smart plugin system for Teloxide bots |
homepage | |
repository | https://github.com/Junaid433/Teloxide-Plugins |
max_upload_size | |
id | 1854103 |
size | 76,941 |
The easiest way to create Telegram bots with Rust!
Smart plugin system for Teloxide bots - write less code, do more!
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! 🎉
async
/await
cargo new my-awesome-bot
cd my-awesome-bot
Add to your Cargo.toml
:
[dependencies]
teloxide = "0.17"
teloxide-plugins = "0.1.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
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;
}
/newbot
and follow the instructions"YOUR_BOT_TOKEN"
in the codecargo run
Test it by sending /ping
or hello
to your bot! 🎉
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:
/start
OR /help
!start
OR !help
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-insensitiveHandle 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();
}
}
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();
}
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;
}
}
}
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();
}
#[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();
}
}
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();
}
}
}
}
#[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();
}
}
#[TeloxidePlugin]
AttributesAttribute | 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" |
PluginContext
: Contains bot instance, message, and callback querydispatch()
: Main function that routes messages to appropriate pluginsPluginMeta
: Metadata structure for plugin registrationPlugins are registered during static initialization, before the tokio runtime starts:
#[ctor::ctor]
fn plugin_constructor() {
register_plugin(&plugin_metadata);
}
Once the bot is running, all operations are fully async:
await
points#[TeloxidePlugin]
attributes are correctcargo run
and look for startup messagesCargo.toml
are compatible(?i)
flag for case-insensitive matching^
and $
for exact matchesEnable 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();
}
We welcome contributions! Here's how to get involved:
git checkout -b feature/amazing-feature
)git commit -m 'Add amazing feature'
)git push origin feature/amazing-feature
)git clone https://github.com/Junaid433/teloxide-plugins.git
cd teloxide-plugins
cargo test
cargo run --example bot
This project is licensed under the MIT License - see the LICENSE file for details.
Happy bot building! 🤖✨
Made with ❤️ for the Rust community