telega/router
Telega Router
The router module provides a flexible and composable routing system for Telegram bot updates. It allows you to define handlers for different types of messages and organize them into logical groups with middleware support, error handling, and composition capabilities.
Basic Usage
import telega/router
import telega/update
import telega/reply
let router =
router.new("my_bot")
|> router.on_command("start", handle_start)
|> router.on_command("help", handle_help)
|> router.on_any_text(handle_text)
|> router.on_photo(handle_photo)
|> router.fallback(handle_unknown)
Routing Priority
Routes are matched in the following priority order:
- Commands - Exact command matches (e.g., “/start”, “/help”)
- Callback Queries - Callback data patterns
- Custom Routes - User-defined matchers
- Media Routes - Photo, video, voice, audio handlers
- Text Routes - Text pattern matching
- Fallback - Catch-all handler for unmatched updates
Within each category, routes are tried in the order they were added, with the first matching route handling the update.
Pattern Matching
Text and callback queries support flexible pattern matching:
router
|> router.on_text(Exact("hello"), handle_hello)
|> router.on_text(Prefix("search:"), handle_search)
|> router.on_text(Contains("help"), handle_help_mention)
|> router.on_text(Suffix("?"), handle_question)
router
|> router.on_callback(Prefix("page:"), handle_pagination)
|> router.on_callback(Exact("cancel"), handle_cancel)
Middleware System
Middleware allows you to wrap handlers with additional functionality. Middleware is applied in reverse order of addition (last added runs first):
router
|> router.use_middleware(router.with_logging)
|> router.use_middleware(auth_middleware)
|> router.use_middleware(rate_limit_middleware)
Built-in middleware includes:
with_logging- Logs all update processingwith_filter- Conditionally processes updateswith_recovery- Recovers from handler errors
Error Handling
Routers support catch handlers to gracefully handle errors from routes:
router
|> router.with_catch_handler(fn(error) {
log.error("Route error: " <> string.inspect(error))
Error(error)
})
The catch handler receives only the error (no context) and must return
Result(Context, error) — log and re-raise with Error(error), or recover
with a context already in scope.
Note: The router’s catch handler only handles errors from route handlers.
System-level errors (like session persistence failures) are handled by
the bot’s main catch handler configured via telega.with_catch_handler.
Router Composition
Routers can be composed to build complex routing structures:
Merging Routers
merge combines two routers into one, with all routes unified.
Routes from the first router take priority in case of conflicts:
let admin_router =
router.new("admin")
|> router.on_command("ban", handle_ban)
|> router.on_command("stats", handle_stats)
let user_router =
router.new("user")
|> router.on_command("start", handle_start)
|> router.on_command("help", handle_help)
let main_router = router.merge(admin_router, user_router)
Composing Routers
compose creates a router that tries each sub-router in sequence.
Each router maintains its own middleware and error handling:
let public_router =
router.new("public")
|> router.use_middleware(rate_limiting)
|> router.on_command("start", handle_start)
let private_router =
router.new("private")
|> router.use_middleware(auth_required)
|> router.on_command("admin", handle_admin)
let app = router.compose(private_router, public_router)
Scoped Routing
scope creates a sub-router that only processes updates matching a predicate:
let admin_router =
router.new("admin")
|> router.on_command("ban", handle_ban)
|> router.scope(fn(update) {
// Only process updates from admin users
case update {
update.CommandUpdate(from_id: id, ..) -> is_admin(id)
_ -> False
}
})
Custom Routes
For complex routing logic, use custom matchers:
router
|> router.on_custom(
matcher: fn(update) {
case update {
update.TextUpdate(text: t, ..) ->
string.starts_with(t, "http://") || string.starts_with(t, "https://")
_ -> False
}
},
handler: handle_link
)
Magic Filters
The router includes a powerful filter system for creating complex routing conditions:
// Simple filters
router
|> router.on_filtered(router.is_private_chat(), handle_private)
|> router.on_filtered(router.from_user(admin_id), handle_admin)
// Combining filters with AND logic
router
|> router.on_filtered(
router.and2(
router.is_group_chat(),
router.text_starts_with("!")
),
handle_group_command
)
// Combining multiple filters
router
|> router.on_filtered(
router.and([
router.is_text(),
router.from_users([admin1, admin2, admin3]),
router.not(router.text_starts_with("/"))
]),
handle_admin_text
)
// OR logic for multiple conditions
router
|> router.on_filtered(
router.or([
router.text_equals("help"),
router.text_equals("?"),
router.command_equals("help")
]),
show_help
)
Available Filters
Message Type Filters:
is_text()- Text messagesis_command()- Command messageshas_photo()- Photo messageshas_video()- Video messageshas_media()- Any media (photo, video, audio, voice)is_media_group()- Media group/album messagesis_callback_query()- Callback button presses
Text Content Filters:
text_equals(text)- Exact text matchtext_starts_with(prefix)- Text starts with prefixtext_contains(substring)- Text contains substringcommand_equals(cmd)- Specific command
User/Chat Filters:
from_user(user_id)- From specific userfrom_users(user_ids)- From any of the usersin_chat(chat_id)- In specific chatis_private_chat()- Private messages onlyis_group_chat()- Group/supergroup messages only
Callback Query Filters:
callback_data_starts_with(prefix)- Callback data prefix
Filter Composition:
and(filters)/and2(f1, f2)- All filters must matchor(filters)/or2(f1, f2)- Any filter must matchnot(filter)- Negate a filterfilter(name, check_fn)- Custom filter function
Advanced Features
Multiple Command Handlers
Register the same handler for multiple commands:
router
|> router.on_commands(["start", "help", "about"], show_info)
Media Handling
Handle different media types with dedicated handlers:
router
|> router.on_photo(handle_photo)
|> router.on_video(handle_video)
|> router.on_voice(handle_voice_message)
|> router.on_audio(handle_audio_file)
|> router.on_media_group(handle_media_album)
Handler Types
The router provides type-safe handlers for different update types:
CommandHandler- Receives parsed command with argumentsTextHandler- Receives text stringCallbackHandler- Receives callback query ID and dataPhotoHandler- Receives list of photo sizesVideoHandler- Receives video objectVoiceHandler- Receives voice messageAudioHandler- Receives audio fileMediaGroupHandler- Receives media group ID and list of messagesHandler- Generic handler for any update type
Types
pub type AudioHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), types.Audio) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type CallbackHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), String, String) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type ChatJoinRequestHandler(session, error, dependencies) =
fn(
bot.Context(session, error, dependencies),
types.ChatJoinRequest,
) -> Result(bot.Context(session, error, dependencies), error)
pub type ChatMemberUpdatedHandler(session, error, dependencies) =
fn(
bot.Context(session, error, dependencies),
types.ChatMemberUpdated,
) -> Result(bot.Context(session, error, dependencies), error)
pub type ChosenInlineResultHandler(
session,
error,
dependencies,
) =
fn(
bot.Context(session, error, dependencies),
types.ChosenInlineResult,
) -> Result(bot.Context(session, error, dependencies), error)
pub type CommandHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), update.Command) -> Result(
bot.Context(session, error, dependencies),
error,
)
Generic handler type for all updates
pub type Handler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type InlineQueryHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), types.InlineQuery) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type MediaGroupHandler(session, error, dependencies) =
fn(
bot.Context(session, error, dependencies),
String,
List(types.Message),
) -> Result(bot.Context(session, error, dependencies), error)
pub type MessageHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), types.Message) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type MessageReactionCountHandler(
session,
error,
dependencies,
) =
fn(
bot.Context(session, error, dependencies),
types.MessageReactionCountUpdated,
) -> Result(bot.Context(session, error, dependencies), error)
pub type MessageReactionHandler(session, error, dependencies) =
fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error)
Middleware wraps a handler with additional functionality
pub type Middleware(session, error, dependencies) =
fn(
fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error)
Pattern matching for text and callbacks
pub type Pattern {
Exact(String)
Prefix(String)
Contains(String)
Suffix(String)
}
Constructors
-
Exact(String) -
Prefix(String) -
Contains(String) -
Suffix(String)
pub type PhotoHandler(session, error, dependencies) =
fn(
bot.Context(session, error, dependencies),
List(types.PhotoSize),
) -> Result(bot.Context(session, error, dependencies), error)
pub type PollAnswerHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), types.PollAnswer) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type PollHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), types.Poll) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type PreCheckoutQueryHandler(session, error, dependencies) =
fn(
bot.Context(session, error, dependencies),
types.PreCheckoutQuery,
) -> Result(bot.Context(session, error, dependencies), error)
Unified route type that encompasses all route types
pub type Route(session, error, dependencies) {
TextPatternRoute(
pattern: Pattern,
handler: fn(bot.Context(session, error, dependencies), String) -> Result(
bot.Context(session, error, dependencies),
error,
),
)
PhotoRoute(
handler: fn(
bot.Context(session, error, dependencies),
List(types.PhotoSize),
) -> Result(bot.Context(session, error, dependencies), error),
)
VideoRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.Video,
) -> Result(bot.Context(session, error, dependencies), error),
)
VoiceRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.Voice,
) -> Result(bot.Context(session, error, dependencies), error),
)
AudioRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.Audio,
) -> Result(bot.Context(session, error, dependencies), error),
)
MediaGroupRoute(
handler: fn(
bot.Context(session, error, dependencies),
String,
List(types.Message),
) -> Result(bot.Context(session, error, dependencies), error),
)
InlineQueryRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.InlineQuery,
) -> Result(bot.Context(session, error, dependencies), error),
)
ChosenInlineResultRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.ChosenInlineResult,
) -> Result(bot.Context(session, error, dependencies), error),
)
ShippingQueryRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.ShippingQuery,
) -> Result(bot.Context(session, error, dependencies), error),
)
PreCheckoutQueryRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.PreCheckoutQuery,
) -> Result(bot.Context(session, error, dependencies), error),
)
PollRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.Poll,
) -> Result(bot.Context(session, error, dependencies), error),
)
PollAnswerRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.PollAnswer,
) -> Result(bot.Context(session, error, dependencies), error),
)
MessageReactionRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
)
MessageReactionEmojiRoute(
emojis: List(String),
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
)
MessageReactionPaidRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
)
MessageReactionAddedRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
)
MessageReactionRemovedRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
)
MessageReactionCountRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionCountUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
)
ChatMemberUpdatedRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.ChatMemberUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
)
ChatJoinRequestRoute(
handler: fn(
bot.Context(session, error, dependencies),
types.ChatJoinRequest,
) -> Result(bot.Context(session, error, dependencies), error),
)
CustomRoute(
matcher: fn(update.Update) -> Bool,
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
)
FilteredRoute(
filter: Filter,
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
)
}
Constructors
-
TextPatternRoute( pattern: Pattern, handler: fn(bot.Context(session, error, dependencies), String) -> Result( bot.Context(session, error, dependencies), error, ), ) -
PhotoRoute( handler: fn( bot.Context(session, error, dependencies), List(types.PhotoSize), ) -> Result(bot.Context(session, error, dependencies), error), ) -
VideoRoute( handler: fn( bot.Context(session, error, dependencies), types.Video, ) -> Result(bot.Context(session, error, dependencies), error), ) -
VoiceRoute( handler: fn( bot.Context(session, error, dependencies), types.Voice, ) -> Result(bot.Context(session, error, dependencies), error), ) -
AudioRoute( handler: fn( bot.Context(session, error, dependencies), types.Audio, ) -> Result(bot.Context(session, error, dependencies), error), ) -
MediaGroupRoute( handler: fn( bot.Context(session, error, dependencies), String, List(types.Message), ) -> Result(bot.Context(session, error, dependencies), error), ) -
InlineQueryRoute( handler: fn( bot.Context(session, error, dependencies), types.InlineQuery, ) -> Result(bot.Context(session, error, dependencies), error), ) -
ChosenInlineResultRoute( handler: fn( bot.Context(session, error, dependencies), types.ChosenInlineResult, ) -> Result(bot.Context(session, error, dependencies), error), ) -
ShippingQueryRoute( handler: fn( bot.Context(session, error, dependencies), types.ShippingQuery, ) -> Result(bot.Context(session, error, dependencies), error), ) -
PreCheckoutQueryRoute( handler: fn( bot.Context(session, error, dependencies), types.PreCheckoutQuery, ) -> Result(bot.Context(session, error, dependencies), error), ) -
PollRoute( handler: fn( bot.Context(session, error, dependencies), types.Poll, ) -> Result(bot.Context(session, error, dependencies), error), ) -
PollAnswerRoute( handler: fn( bot.Context(session, error, dependencies), types.PollAnswer, ) -> Result(bot.Context(session, error, dependencies), error), ) -
MessageReactionRoute( handler: fn( bot.Context(session, error, dependencies), types.MessageReactionUpdated, ) -> Result(bot.Context(session, error, dependencies), error), ) -
MessageReactionEmojiRoute( emojis: List(String), handler: fn( bot.Context(session, error, dependencies), types.MessageReactionUpdated, ) -> Result(bot.Context(session, error, dependencies), error), ) -
MessageReactionPaidRoute( handler: fn( bot.Context(session, error, dependencies), types.MessageReactionUpdated, ) -> Result(bot.Context(session, error, dependencies), error), ) -
MessageReactionAddedRoute( handler: fn( bot.Context(session, error, dependencies), types.MessageReactionUpdated, ) -> Result(bot.Context(session, error, dependencies), error), ) -
MessageReactionRemovedRoute( handler: fn( bot.Context(session, error, dependencies), types.MessageReactionUpdated, ) -> Result(bot.Context(session, error, dependencies), error), ) -
MessageReactionCountRoute( handler: fn( bot.Context(session, error, dependencies), types.MessageReactionCountUpdated, ) -> Result(bot.Context(session, error, dependencies), error), ) -
ChatMemberUpdatedRoute( handler: fn( bot.Context(session, error, dependencies), types.ChatMemberUpdated, ) -> Result(bot.Context(session, error, dependencies), error), ) -
ChatJoinRequestRoute( handler: fn( bot.Context(session, error, dependencies), types.ChatJoinRequest, ) -> Result(bot.Context(session, error, dependencies), error), ) -
CustomRoute( matcher: fn(update.Update) -> Bool, handler: fn( bot.Context(session, error, dependencies), update.Update, ) -> Result(bot.Context(session, error, dependencies), error), ) -
FilteredRoute( filter: Filter, handler: fn( bot.Context(session, error, dependencies), update.Update, ) -> Result(bot.Context(session, error, dependencies), error), )
Router with unified routes and middleware support
pub opaque type Router(session, error, dependencies)
pub type ShippingQueryHandler(session, error, dependencies) =
fn(
bot.Context(session, error, dependencies),
types.ShippingQuery,
) -> Result(bot.Context(session, error, dependencies), error)
pub type TextHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), String) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type VideoHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), types.Video) -> Result(
bot.Context(session, error, dependencies),
error,
)
pub type VoiceHandler(session, error, dependencies) =
fn(bot.Context(session, error, dependencies), types.Voice) -> Result(
bot.Context(session, error, dependencies),
error,
)
Values
pub fn allowed_updates(
router: Router(session, error, dependencies),
) -> List(String)
Derive the set of Telegram update types this router actually handles, as the
strings expected by allowed_updates (e.g. "message", "callback_query").
The result is deduplicated and sorted for stable output.
If the router has a fallback, custom, or filtered route, the handled set cannot be determined statically (those routes can match anything), so an empty list is returned to signal “do not restrict” — Telegram then sends its default update set. Use a manual override when you need narrowing alongside catch-all routes.
pub fn callback_data_starts_with(prefix: String) -> Filter
Filter for callback data that starts with prefix
pub fn compose(
first: Router(session, error, dependencies),
second: Router(session, error, dependencies),
) -> Router(session, error, dependencies)
Compose two routers, where each router maintains its own middleware and catch handlers. First router is tried first, if it doesn’t handle the update, second router is tried.
pub fn compose_many(
routers: List(Router(session, error, dependencies)),
) -> Router(session, error, dependencies)
Compose multiple routers into one. Routers are tried in order. Each router maintains its own middleware and catch handlers.
pub fn fallback(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Set fallback handler for unmatched updates
pub fn filter(
name: String,
check: fn(update.Update) -> Bool,
) -> Filter
Create a filter from a custom function
pub fn from_chats(chat_ids: List(Int)) -> Filter
Filter by multiple chat IDs. Matches when the update’s chat is one of
chat_ids — a whitelist of chats. Combine with not for a blacklist:
// Only react in the support chats
router.on_filtered(router.from_chats([-100_1, -100_2]), handler)
// React everywhere except the banned chats
router.on_filtered(router.not(router.from_chats([-100_666])), handler)
pub fn handle(
router: Router(session, error, dependencies),
ctx: bot.Context(session, error, dependencies),
update: update.Update,
) -> Result(bot.Context(session, error, dependencies), error)
Process an update through the router
pub fn is_group_chat() -> Filter
Filter for group chats https://core.telegram.org/api/bots%2Fids#supergroup-channel-ids
pub fn is_private_chat() -> Filter
Filter for private chats https://core.telegram.org/api/bots%2Fids#user-ids
pub fn merge(
first: Router(session, error, dependencies),
second: Router(session, error, dependencies),
) -> Router(session, error, dependencies)
Merge two routers into one. All routes are combined, with first router’s routes taking priority in case of conflicts. Middleware and catch handlers are shared.
pub fn on_any_text(
router: Router(session, error, dependencies),
handler: fn(bot.Context(session, error, dependencies), String) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> Router(session, error, dependencies)
Add a handler for any text
pub fn on_audio(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.Audio,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
pub fn on_callback(
router: Router(session, error, dependencies),
pattern: Pattern,
handler: fn(
bot.Context(session, error, dependencies),
String,
String,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add a callback query handler with pattern
pub fn on_chat_join_request(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.ChatJoinRequest,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for chat join requests
pub fn on_chat_member_updated(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.ChatMemberUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for chat member updates
pub fn on_chosen_inline_result(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.ChosenInlineResult,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for chosen inline results
pub fn on_command(
router: Router(session, error, dependencies),
command: String,
handler: fn(
bot.Context(session, error, dependencies),
update.Command,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add a command handler
pub fn on_command_with_description(
router: Router(session, error, dependencies),
command: String,
description: String,
handler: fn(
bot.Context(session, error, dependencies),
update.Command,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add a command handler together with a human-readable description.
The description is what shows up in the Telegram command menu. When the bot
is started with telega.with_auto_commands, all commands registered this way
are published via setMyCommands automatically, and telega_i18n can supply
per-language variants. The description is ignored for routing — it only feeds
command auto-synchronization.
router
|> router.on_command_with_description("start", "Start the bot", handle_start)
|> router.on_command_with_description("help", "Show help", handle_help)
pub fn on_commands(
router: Router(session, error, dependencies),
commands: List(String),
handler: fn(
bot.Context(session, error, dependencies),
update.Command,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add multiple commands with same handler
pub fn on_custom(
router: Router(session, error, dependencies),
matcher: fn(update.Update) -> Bool,
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add a custom route with matcher function
pub fn on_filtered(
router: Router(session, error, dependencies),
filter: Filter,
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add a filtered route
pub fn on_inline_query(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.InlineQuery,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for inline queries
pub fn on_media_group(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
String,
List(types.Message),
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for media groups (albums of photos/videos)
pub fn on_paid_reaction(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for paid reactions (stars)
Example
router
|> router.on_paid_reaction(handle_star_reaction)
pub fn on_photo(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
List(types.PhotoSize),
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handlers for media types
pub fn on_poll(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.Poll,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for poll updates
pub fn on_poll_answer(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.PollAnswer,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for poll answer updates
pub fn on_pre_checkout_query(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.PreCheckoutQuery,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for pre-checkout queries (payments)
pub fn on_reaction(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for message reactions
pub fn on_reaction_added(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for added reactions only (filters out removed reactions)
Example
router
|> router.on_reaction_added(handle_new_reaction)
pub fn on_reaction_count(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionCountUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for message reaction count updates (anonymous reactions in channels)
Example
router
|> router.on_reaction_count(handle_reaction_counts)
pub fn on_reaction_emoji(
router: Router(session, error, dependencies),
emoji: String,
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for a specific emoji reaction
Example
router
|> router.on_reaction_emoji("👍", handle_like)
pub fn on_reaction_emojis(
router: Router(session, error, dependencies),
emojis: List(String),
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for multiple emoji reactions
Example
router
|> router.on_reaction_emojis(["👍", "❤", "🔥"], handle_positive_reactions)
pub fn on_reaction_removed(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.MessageReactionUpdated,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for removed reactions only (filters out added reactions)
Example
router
|> router.on_reaction_removed(handle_removed_reaction)
pub fn on_shipping_query(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.ShippingQuery,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add handler for shipping queries (payments)
pub fn on_text(
router: Router(session, error, dependencies),
pattern: Pattern,
handler: fn(bot.Context(session, error, dependencies), String) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> Router(session, error, dependencies)
Add a text handler with pattern
pub fn on_video(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.Video,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
pub fn on_voice(
router: Router(session, error, dependencies),
handler: fn(
bot.Context(session, error, dependencies),
types.Voice,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
pub fn registered_commands(
router: Router(session, error, dependencies),
) -> List(#(String, String))
List every command registered with a description, as #(command, description)
pairs sorted by command name. Commands added with on_command (no
description) are omitted. Flattens composed routers, so a fully composed
router reports the union of its sub-routers’ described commands.
This is what telega.with_auto_commands feeds into setMyCommands.
pub fn scope(
router: Router(session, error, dependencies),
predicate: fn(update.Update) -> Bool,
) -> Router(session, error, dependencies)
Create a sub-router that processes updates within its own scope
pub fn text_contains(substring: String) -> Filter
Filter for text that contains a substring
pub fn text_equals(text: String) -> Filter
Filter for text that equals a specific value
pub fn text_starts_with(prefix: String) -> Filter
Filter for text that starts with a prefix
pub fn use_middleware(
router: Router(session, error, dependencies),
middleware: fn(
fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Router(session, error, dependencies)
Add middleware to the router
pub fn with_catch_handler(
router: Router(session, error, dependencies),
catch_handler: fn(error) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> Router(session, error, dependencies)
Add a catch handler to the router that handles errors from all routes
pub fn with_filter(
predicate: fn(update.Update) -> Bool,
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
)
Filter middleware - only process updates that match predicate
pub fn with_logging(
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
)
Logging middleware - logs update processing
pub fn with_rate_limit(
limit limit: Int,
window_ms window_ms: Int,
on_limit on_limit: fn(bot.Context(session, error, dependencies)) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> fn(
fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
)
Per-user flood control middleware: allows at most limit updates per
window_ms window for each {chat_id}:{from_id} pair. Counters live in
ETS, so the limit is shared across all routes of the bot.
on_limit is called instead of the handler when the limit is exceeded —
pass fn(ctx) { Ok(ctx) } to drop the update silently, or reply from it
to inform the user. Every rejected update emits a
telega.rate_limit.hit telemetry event.
Updates without user context (e.g. poll updates, from_id is -1) are
not limited.
router.new("bot")
|> router.use_middleware(router.with_rate_limit(
limit: 5,
window_ms: 3000,
on_limit: fn(ctx) { Ok(ctx) },
))
Call with_rate_limit once at bot setup: the limiter’s ETS table is owned
by the calling process and is deleted when that process exits.
pub fn with_recovery(
recover: fn(error) -> Result(
bot.Context(session, error, dependencies),
error,
),
handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> fn(bot.Context(session, error, dependencies), update.Update) -> Result(
bot.Context(session, error, dependencies),
error,
)
Error recovery middleware