Smart message renderer for Aiogram with keyboard builder and JSON loader
Project description
aiogram-smart-messages
Smart message renderer for Aiogram - A powerful toolkit for building sophisticated Telegram bots with JSON-based message templates, dynamic keyboards, and robust error handling.
โจ Features
- ๐ฏ JSON-based message templates - Organize messages by namespace, role, and language
- โจ๏ธ Advanced keyboard builder - Create inline and reply keyboards with WebApp support
- ๐ Smart message operations - Send, edit, reply with automatic method selection
- ๐ฌ Notificator service - High-level API for sending notifications to users
- ๐ Multi-language support - Built-in localization system
- ๐จ Context formatting - Dynamic message content with variable substitution
- ๐ก๏ธ Error handling - Comprehensive logging with customizable decorators
- ๐ฆ Type-safe - Full type hints for better IDE support
- โก Async-first - Built for modern async/await patterns
- ๐ Middleware support - Easy integration with Aiogram dispatcher
๐ Requirements
- Python 3.10+
- aiogram 3.x
๐ Installation
pip install aiogram-smart-messages
Or install from source:
git clone https://github.com/yourusername/aiogram-smart-messages.git
cd aiogram-smart-messages
pip install -e .
๐ Quick Start
1. Basic Setup with Middleware (Recommended)
from aiogram import Bot, Dispatcher
from aiogram.types import Message
from aiogram_smart_messages.middleware import MessageEngineMiddleware
from aiogram_smart_messages.renderer.bot_messages import SmartMessageRenderer
from aiogram_smart_messages.logger import get_logger
# Initialize bot and dispatcher
bot = Bot(token="YOUR_BOT_TOKEN")
dp = Dispatcher()
logger = get_logger("my_bot")
# Register middleware
dp.message.middleware(MessageEngineMiddleware(bot))
dp.callback_query.middleware(MessageEngineMiddleware(bot))
# Now msg_engine is automatically available in handlers
@dp.message(commands=["start"])
async def cmd_start(message: Message, msg_engine):
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
role="user",
namespace="main",
menu_file="welcome",
block_key="greeting",
lang="en",
context={"username": message.from_user.first_name}
)
2. Alternative: Manual Setup
from aiogram import Bot, Dispatcher
from aiogram.types import Message
from aiogram_smart_messages.message_engine import MessageEngine
from aiogram_smart_messages.renderer.bot_messages import SmartMessageRenderer
# Initialize bot and engine manually
bot = Bot(token="YOUR_BOT_TOKEN")
dp = Dispatcher()
engine = MessageEngine(bot)
@dp.message(commands=["start"])
async def cmd_start(message: Message):
await SmartMessageRenderer.send(
engine=engine,
source=message,
role="user",
namespace="main",
menu_file="welcome",
block_key="greeting",
lang="en",
context={"username": message.from_user.first_name}
)
3. Using NotificatorService
For sending notifications to users (e.g., from background tasks, admin panels, or scheduled jobs):
from aiogram import Bot
from aiogram_smart_messages import NotificatorService
bot = Bot(token="YOUR_BOT_TOKEN")
# Send notification to specific user
async def notify_user(user_id: int):
notificator = NotificatorService(user_id=user_id, bot=bot)
# Simple text notification
await notificator.notify_message(
text="Your order has been processed!"
)
# Or use smart message with JSON template
await notificator.notify_message(
smart_data={
"role": "user",
"namespace": "shop",
"menu_file": "orders",
"block_key": "order_complete",
"lang": "en",
"context": {"order_id": "12345"}
}
)
# Example: Batch notifications
async def notify_all_users(user_ids: list[int]):
for user_id in user_ids:
notificator = NotificatorService(user_id=user_id, bot=bot)
await notificator.notify_message(text="Important update!")
4. JSON Message Structure
Create message files in your project structure:
my_bot/
โโโ messages/
โ โโโ user/
โ โ โโโ en/
โ โ โ โโโ welcome.json
โ โ โโโ ru/
โ โ โโโ welcome.json
โ โโโ admin/
โ โโโ ...
Example welcome.json:
{
"greeting": {
"text": "Hello, {username}! Welcome to our bot.",
"buttons": [
[
{"type": "callback", "text": "Get Started", "data": "start"},
{"type": "url", "text": "Help", "data": "https://example.com/help"}
],
[
{"type": "webapp", "text": "Open App", "data": "https://app.example.com"}
]
]
},
"main_menu": {
"text": "Choose an option:",
"photo": "menu_photo.jpg",
"caption": "Main Menu",
"buttons": [
[
{"type": "callback", "text": "Settings โ๏ธ", "data": "settings"},
{"type": "callback", "text": "Profile ๐ค", "data": "profile"}
]
]
},
"confirm_action": {
"text": "Are you sure?",
"buttons": [
[
{"text": "โ
Yes", "style": "success"},
{"text": "โ No", "style": "danger"}
],
[
{"text": "Learn more", "style": "primary", "icon_custom_emoji_id": "5368324170671202286"}
]
]
}
}
5. Using Middleware in All Handlers
from aiogram.types import CallbackQuery
from aiogram_smart_messages.decorators import with_error_logging
# Message handler with middleware
@dp.message(commands=["profile"])
async def show_profile(message: Message, msg_engine):
user_data = await get_user_data(message.from_user.id)
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
role="user",
namespace="main",
menu_file="profile",
block_key="view",
lang="en",
context={
"username": user_data["name"],
"balance": user_data["balance"],
"level": user_data["level"]
}
)
# Callback handler with middleware
@dp.callback_query(lambda c: c.data == "settings")
async def show_settings(callback: CallbackQuery, msg_engine):
await SmartMessageRenderer.edit(
engine=msg_engine,
source=callback,
role="user",
namespace="main",
menu_file="settings",
block_key="main",
lang="en"
)
await callback.answer()
# Error handling with decorator
@with_error_logging(logger=logger, error_label="WELCOME_MESSAGE")
async def send_personalized_welcome(message: Message, msg_engine, user_data: dict):
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
role="user",
namespace="main",
menu_file="welcome",
block_key="greeting",
lang=user_data.get("language", "en"),
context={
"username": user_data["name"],
"balance": user_data["balance"]
}
)
6. Advanced Usage
Edit Messages
@dp.callback_query(lambda c: c.data == "settings")
async def show_settings(callback: CallbackQuery, msg_engine):
await SmartMessageRenderer.edit(
engine=msg_engine,
source=callback,
role="user",
namespace="main",
menu_file="settings",
block_key="main",
lang="en"
)
await callback.answer()
Smart Edit or Send
Automatically decides whether to edit or resend based on media content:
@dp.callback_query(lambda c: c.data.startswith("view_"))
async def view_item(callback: CallbackQuery, msg_engine):
item_id = callback.data.split("_")[1]
await SmartMessageRenderer.smart_edit_or_send(
engine=msg_engine,
source=callback,
role="user",
namespace="shop",
menu_file="catalog",
block_key="item_details",
lang="en",
context={"item_id": item_id}
)
Reply to Messages
@dp.message(lambda m: m.text and "help" in m.text.lower())
async def help_reply(message: Message, msg_engine):
await SmartMessageRenderer.reply(
engine=msg_engine,
source=message,
role="user",
namespace="main",
menu_file="help",
block_key="quick_help",
lang="en"
)
Send Documents
from aiogram.types import FSInputFile
@dp.callback_query(lambda c: c.data == "download_report")
async def send_report(callback: CallbackQuery, msg_engine):
doc = FSInputFile("reports/monthly_report.pdf")
await SmartMessageRenderer.send_document(
engine=msg_engine,
source=callback.message.chat.id,
document=doc,
caption="Your monthly report"
)
await callback.answer("Report sent!")
7. Building Keyboards Manually
from aiogram_smart_messages.builder import KeyboardBuilder
# Inline keyboard
inline_buttons = [
[
{"type": "callback", "text": "Option 1", "data": "opt1"},
{"type": "callback", "text": "Option 2", "data": "opt2"}
],
[
{"type": "url", "text": "Visit Website", "data": "https://example.com"}
]
]
inline_kb = KeyboardBuilder.build_inline_keyboard(inline_buttons)
# Reply keyboard
reply_buttons = [
[{"text": "Main Menu"}, {"text": "Settings"}],
[{"type": "webapp", "text": "Open App", "data": "https://app.example.com"}]
]
reply_kb = KeyboardBuilder.build_reply_keyboard(
reply_buttons,
resize_keyboard=True,
one_time_keyboard=True
)
# Reply keyboard with button styles and custom emoji (Telegram 2026+)
styled_buttons = [
[
{"text": "Confirm", "style": "success", "icon_custom_emoji_id": "5368324170671202286"},
{"text": "Cancel", "style": "danger"},
],
[
{"text": "More info", "style": "primary"},
],
]
styled_kb = KeyboardBuilder.build_reply_keyboard(styled_buttons)
8. Using Decorators
Error Logging Decorator
from aiogram_smart_messages.decorators import with_error_logging
@with_error_logging(
logger=logger,
fallback=None,
error_label="DATABASE_ERROR"
)
async def get_user_data(user_id: int):
# Your database logic here
data = await db.fetch_user(user_id)
return data
# With re-raise for critical operations
@with_error_logging(
logger=logger,
reraise=True,
error_label="CRITICAL_PAYMENT"
)
async def process_payment(amount: float):
# Critical operation that should fail loudly
await payment_gateway.charge(amount)
9. Logger Configuration
from aiogram_smart_messages.logger import get_logger
import logging
# Basic logger
logger = get_logger("my_bot")
# Custom configuration
logger = get_logger(
name="my_bot",
level=logging.DEBUG,
format_string="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
# Use logger
logger.info("Bot started")
logger.warning("Rate limit approaching")
logger.error("Failed to send message")
logger.debug("Processing callback: %s", callback_data)
10. Working with Complex Context Objects
You can pass entire objects (like database models, dataclasses, or dictionaries) into context and access their attributes in JSON templates:
from dataclasses import dataclass
from datetime import datetime
@dataclass
class UserData:
username: str
balance: float
level: int
created_at: datetime
is_premium: bool
# Get user from database
user_data = await db.get_user(user_id) # Returns UserData object
# Pass entire object to context
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
role="user",
namespace="main",
menu_file="profile",
block_key="detailed_view",
lang="en",
context={
"user_data": user_data, # Pass entire object
"current_date": datetime.now()
}
)
Example JSON template (profile.json):
{
"detailed_view": {
"text": "๐ค Profile: {user_data.username}\n๐ฐ Balance: ${user_data.balance:.2f}\nโญ Level: {user_data.level}\n๐
Member since: {user_data.created_at:%Y-%m-%d}\n{'โจ Premium' if user_data.is_premium else '๐ Free'}",
"buttons": [
[
{"type": "callback", "text": "Top Up Balance", "data": "topup_{user_data.username}"},
{"type": "callback", "text": "Upgrade", "data": "upgrade_{user_data.level}"}
]
]
}
}
Supported context types:
- โ Dataclasses and Pydantic models
- โ Database ORM objects (SQLAlchemy, Tortoise ORM, etc.)
- โ Nested dictionaries
- โ
Custom classes with
__dict__ - โ DateTime objects with formatting
Advanced formatting examples:
# Nested objects
context = {
"user": {
"profile": {
"name": "John",
"settings": {
"theme": "dark"
}
}
}
}
# In JSON: "Welcome, {user.profile.name}! Theme: {user.profile.settings.theme}"
# Lists and indexing
context = {
"items": ["Apple", "Banana", "Orange"]
}
# In JSON: "First item: {items[0]}"
# Number formatting
context = {
"price": 1234.5678
}
# In JSON: "Price: ${price:.2f}" โ "Price: $1234.57"
# Date formatting
context = {
"date": datetime(2025, 1, 15)
}
# In JSON: "Date: {date:%B %d, %Y}" โ "Date: January 15, 2025"
11. Advanced Features
Extra Keyboard Buttons
Add dynamic buttons to existing keyboard layouts:
extra_buttons = [
{"type": "callback", "text": "๐ Back", "data": "back"}
]
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
role="user",
namespace="main",
menu_file="menu",
block_key="main",
lang="en",
extra_kb=extra_buttons,
extra_kb_position="bottom" # or "top"
)
Override Block Data
Send messages without loading from JSON:
custom_block = {
"text": "Dynamic message",
"buttons": [
[{"type": "callback", "text": "OK", "data": "ok"}]
]
}
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
role="user",
override_block=custom_block
)
Custom Keyboard Markup
Use your own pre-built keyboards:
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
custom_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Custom", callback_data="custom")]
])
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
role="user",
namespace="main",
menu_file="menu",
block_key="main",
lang="en",
custom_markup=custom_keyboard
)
๐ Middleware
MessageEngineMiddleware
The library includes a convenient middleware that automatically injects MessageEngine into your handlers.
from aiogram_smart_messages.middleware import MessageEngineMiddleware
# Register for messages
dp.message.middleware(MessageEngineMiddleware(bot))
# Register for callback queries
dp.callback_query.middleware(MessageEngineMiddleware(bot))
# Now use msg_engine in handlers
@dp.message(commands=["start"])
async def cmd_start(message: Message, msg_engine):
await SmartMessageRenderer.send(
engine=msg_engine,
source=message,
# ... other parameters
)
Benefits:
- โ
No need to create
MessageEnginemanually in each handler - โ Cleaner code organization
- โ Consistent engine instance across all handlers
- โ Easy to test and mock
๐ฏ Supported Button Types
Inline Keyboards
callback- Callback query buttonurl- URL button (opens link)webapp- Web App buttonswitch_inline_query- Inline query switch
Reply Keyboards
- Regular text button (default)
webapp- Web App buttonstyleโ button color:"danger"(red),"success"(green),"primary"(blue)icon_custom_emoji_idโ custom emoji shown before the button text
๐ Project Structure Example
my_bot/
โโโ main_bot/
โ โโโ messages/
โ โ โโโ user/
โ โ โ โโโ en/
โ โ โ โ โโโ welcome.json
โ โ โ โ โโโ menu.json
โ โ โ โ โโโ settings.json
โ โ โ โโโ ru/
โ โ โ โโโ ... (same structure)
โ โ โโโ admin/
โ โ โโโ ... (admin messages)
โ โโโ photos/
โ โ โโโ user/
โ โ โ โโโ en/
โ โ โ โ โโโ menu_photo.jpg
โ โ โ โโโ ru/
โ โ โ โโโ menu_photo.jpg
โ โ โโโ admin/
โ โ โโโ ...
โโโ main.py
โโโ requirements.txt
๐ง Configuration
The library uses the following configuration structure:
module- Top-level module name (default: "main_bot")namespace- Feature namespace (e.g., "main", "admin_panel", "shop")role- User role (e.g., "user", "admin", "common")lang- Language code (e.g., "en", "ru", "es")
File paths:
- Messages:
{module}/messages/{role}/{lang}/{menu_file}.json - Photos:
{module}/{namespace}/photos/{role}/{lang}/{photo_file}
๐ Error Handling
The library provides comprehensive error handling with clear labels:
# Labels used in SmartMessageRenderer:
# - SEND_SMART_MESSAGE
# - EDIT_SMART_MESSAGE
# - REPLY_SMART_MESSAGE
# - SEND_DOCUMENT
# - LOAD_JSON
# - PARSE_TO_SMART
# Custom error handling
@with_error_logging(
logger=logger,
fallback={"status": "error"},
error_label="CUSTOM_OPERATION"
)
async def my_operation():
# Your code here
pass
๐ API Reference
SmartMessageRenderer
Main class for message rendering operations.
Methods:
load_json()- Load message block from JSON fileparse_to_smart()- Parse JSON block to SmartMessage objectsend()- Send a new messageedit()- Edit existing messagesmart_edit_or_send()- Smart decision: edit or resendreply()- Reply to a messagesend_document()- Send document file
MessageEngine
Core engine for message operations.
Methods:
send_smart_message()- Send SmartMessageedit_smart_message()- Edit SmartMessagereply_smart_message()- Reply with SmartMessagesend_document()- Send document
MessageEngineMiddleware
Middleware for automatic MessageEngine injection.
class MessageEngineMiddleware(BaseMiddleware):
def __init__(self, bot: Bot):
# Initializes MessageEngine with bot instance
KeyboardBuilder
Utility for building keyboards.
Methods:
build_inline_keyboard()- Build inline keyboardbuild_reply_keyboard()- Build reply keyboard
NotificatorService
High-level service for sending notifications to users.
class NotificatorService:
def __init__(self, user_id: int, bot: Bot):
# Initialize notificator for specific user
Methods:
notify_message()- Send text or smart message notificationnotify_document()- Send document notification
Usage Example:
from aiogram import Bot
from aiogram.types import FSInputFile
from aiogram_smart_messages import NotificatorService
# Initialize
bot = Bot(token="YOUR_BOT_TOKEN")
notificator = NotificatorService(user_id=123456789, bot=bot)
# Simple text notification
await notificator.notify_message(
text="Your order has been processed"
)
# Smart message notification with JSON template
await notificator.notify_message(
smart_data={
"role": "user",
"namespace": "shop",
"menu_file": "orders",
"block_key": "order_complete",
"lang": "en",
"context": {"order_id": "12345", "total": 99.99}
}
)
# Document notification
doc = FSInputFile("invoice.pdf")
await notificator.notify_document(
document=doc,
caption="Your invoice for order #12345"
)
Use cases:
- Background job notifications (order processing, payments, etc.)
- Scheduled notifications (reminders, alerts)
- Admin notifications to users
- Batch notifications to multiple users
Benefits:
- Clean, simple API for notifications
- Integrates seamlessly with SmartMessageRenderer
- Automatic error logging
- Type-safe with full type hints
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
๐ License
MIT License ยฉ 2025 Kriva
๐ Links
๐ก Tips
- Use middleware - Register
MessageEngineMiddlewarefor cleaner code - Organize messages by feature - Use namespaces to separate different bot sections
- Use context variables - Make messages dynamic and personalized
- Leverage smart_edit_or_send - Let the library decide the best update method
- Add error labels - Make debugging easier with descriptive error labels
- Cache static content - Messages without context are automatically cached
- Use type hints - Full type support for better IDE experience
๐ Examples
Check out more examples in the /examples directory:
- Basic bot setup with middleware
- Multi-language support
- E-commerce bot
- Admin panel
- WebApp integration
Made with โค๏ธ for the Aiogram community
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aiogram_smart_messages-0.3.2.tar.gz.
File metadata
- Download URL: aiogram_smart_messages-0.3.2.tar.gz
- Upload date:
- Size: 28.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f387de63cd4a4eed22f01c04974a440fe4aea8639c103acb5a9c6d9e8fa1a4ec
|
|
| MD5 |
c186278491559a839059495adfeaf937
|
|
| BLAKE2b-256 |
293a486145fbe7e513a3b643bab6d9d235e4e1353e8dd9bda2c692b7bc1b8238
|
File details
Details for the file aiogram_smart_messages-0.3.2-py3-none-any.whl.
File metadata
- Download URL: aiogram_smart_messages-0.3.2-py3-none-any.whl
- Upload date:
- Size: 26.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b60d42ef45ff94a363a2192315c37bc62448220e8f0ebb8b2a7af1777706335b
|
|
| MD5 |
711b5a5ee2cfb10e2dc98c1f6524fd72
|
|
| BLAKE2b-256 |
426e2bfc5ade4f25b1d7c325c43b223147a99d998b40d498732290502d225e5a
|