Skip to main content

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.

Python PyPI version Downloads License: MIT Aiogram

โœจ 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 MessageEngine manually 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 button
  • url - URL button (opens link)
  • webapp - Web App button
  • switch_inline_query - Inline query switch

Reply Keyboards

  • Regular text button (default)
  • webapp - Web App button
  • style โ€” 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 file
  • parse_to_smart() - Parse JSON block to SmartMessage object
  • send() - Send a new message
  • edit() - Edit existing message
  • smart_edit_or_send() - Smart decision: edit or resend
  • reply() - Reply to a message
  • send_document() - Send document file

MessageEngine

Core engine for message operations.

Methods:

  • send_smart_message() - Send SmartMessage
  • edit_smart_message() - Edit SmartMessage
  • reply_smart_message() - Reply with SmartMessage
  • send_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 keyboard
  • build_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 notification
  • notify_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

  1. Use middleware - Register MessageEngineMiddleware for cleaner code
  2. Organize messages by feature - Use namespaces to separate different bot sections
  3. Use context variables - Make messages dynamic and personalized
  4. Leverage smart_edit_or_send - Let the library decide the best update method
  5. Add error labels - Make debugging easier with descriptive error labels
  6. Cache static content - Messages without context are automatically cached
  7. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiogram_smart_messages-0.3.2.tar.gz (28.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiogram_smart_messages-0.3.2-py3-none-any.whl (26.8 kB view details)

Uploaded Python 3

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

Hashes for aiogram_smart_messages-0.3.2.tar.gz
Algorithm Hash digest
SHA256 f387de63cd4a4eed22f01c04974a440fe4aea8639c103acb5a9c6d9e8fa1a4ec
MD5 c186278491559a839059495adfeaf937
BLAKE2b-256 293a486145fbe7e513a3b643bab6d9d235e4e1353e8dd9bda2c692b7bc1b8238

See more details on using hashes here.

File details

Details for the file aiogram_smart_messages-0.3.2-py3-none-any.whl.

File metadata

File hashes

Hashes for aiogram_smart_messages-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b60d42ef45ff94a363a2192315c37bc62448220e8f0ebb8b2a7af1777706335b
MD5 711b5a5ee2cfb10e2dc98c1f6524fd72
BLAKE2b-256 426e2bfc5ade4f25b1d7c325c43b223147a99d998b40d498732290502d225e5a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page