Router

Hierarchical routers with 25+ Pyrogram event decorators, sub-router nesting, and deferred handler registration.

Basic Usage

from pyrogram import filters
from pyrogram_patch.router import Router

router = Router()

@router.on_message(filters.command("start"))
async def start(client, message):
    await message.reply("Hello!")

@router.on_callback_query(filters.regex("^menu_"))
async def handle_menu(client, callback_query):
    await callback_query.answer("Selected!")

Convenience Decorators

Shorthands for common patterns — no need to import filters:

on_command()
# Matches /start — equivalent to on_message(filters.command("start"))
@router.on_command("start")
async def start(client, message):
    await message.reply("Hello!")

# Custom prefix
@router.on_command("help", prefixes="!")
async def help_cmd(client, message):
    ...
on_callback()
# Matches exact callback_data == "profile"
@router.on_callback("profile")
async def show_profile(client, query):
    await query.answer("Opening profile...")

on_callback_data() v0.5.0

Register a callback query handler with regex capture group injection. Named groups ((?P<name>…)) are extracted from query.data and injected as keyword arguments — similar to FastAPI path parameters.

Named groups
@router.on_callback_data(r"page:(?P<num>\d+)")
async def paginate(client, query, num):
    # num is always a string — cast as needed
    page = int(num)
    await query.answer(f"Page {page}")
Positional groups
@router.on_callback_data(r"item:(\d+):(buy|sell)")
async def trade(client, query, group_1, group_2):
    item_id = int(group_1)  # "123"
    action  = group_2       # "buy" or "sell"
    await query.answer(f"{action} item {item_id}")
Tip: For complex structured data, consider CallbackData which handles type coercion and validation automatically.

Sub-Routers

Split handlers across multiple routers for modular bot architecture:

# admin_router.py
admin_router = Router()

@admin_router.on_message(filters.command("ban"))
async def ban_user(client, message):
    ...

# main.py
main_router = Router()
main_router.include_router(admin_router)

app = KurigramClient("my_bot", ...)
app.include_router(main_router)

Context Manager v0.5.0

Use async with for automatic cleanup — handlers are unregistered when the block exits:

async with Router() as router:
    @router.on_message()
    async def handler(client, message):
        ...

    router.set_client(app)
    # ... do work ...
# handlers automatically unregistered here

Available Decorators

on_message
on_callback_query
on_inline_query
on_chosen_inline_result
on_edited_message
on_deleted_messages
on_chat_member_updated
on_chat_join_request
on_raw_update
on_disconnect
on_user_status
on_poll
on_story
on_bot_business_connect
on_bot_business_message
on_edited_bot_business_message
on_deleted_bot_business_messages
on_message_reaction_updated
on_message_reaction_count_updated
on_chat_boost_updated
on_removed_chat_boost
on_pre_checkout_query
on_shipping_query
on_paid_media_purchased

Plus convenience shorthands: on_command(), on_callback(), on_callback_data()

Handler DI

Handlers receive dependencies via their parameter names. Add a patch_helper: PatchHelperparameter to get FSM and data access:

from pyrogram_patch.patch_helper import PatchHelper

@router.on_message(filters.command("profile"))
async def profile(client, message, patch_helper: PatchHelper):
    state = await patch_helper.get_state()
    data = await patch_helper.get_data()
    await message.reply(f"State: {state}, Data: {data}")

Router API

Property / MethodDescription
.clientCurrent attached Pyrogram client (or None)
.is_registeredTrue if handlers are registered with a client
.handler_countNumber of registered handlers
.pending_countHandlers awaiting registration
set_client(app)Attach to a Pyrogram client and register all handlers
include_router(r)Add a sub-router
unregister_handlers()Remove all handlers from the client
clear_handlers()Remove all handlers and sub-routers entirely
get_stats()Returns registration statistics dict