-module(telega@router). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/telega/router.gleam"). -export([new/1, on_command/3, on_commands/3, on_command_with_description/4, on_text/3, on_any_text/2, on_callback/3, on_photo/2, on_video/2, on_voice/2, on_audio/2, on_media_group/2, on_inline_query/2, on_chosen_inline_result/2, on_shipping_query/2, on_pre_checkout_query/2, on_poll/2, on_poll_answer/2, on_reaction/2, on_reaction_emoji/3, on_reaction_emojis/3, on_paid_reaction/2, on_reaction_added/2, on_reaction_removed/2, on_reaction_count/2, on_chat_member_updated/2, on_chat_join_request/2, on_custom/3, on_filtered/3, filter/2, 'and'/1, and2/2, 'or'/1, or2/2, 'not'/1, is_text/0, text_equals/1, text_starts_with/1, text_contains/1, is_command/0, command_equals/1, from_user/1, from_users/1, in_chat/1, from_chats/1, is_private_chat/0, is_group_chat/0, has_photo/0, has_video/0, is_media_group/0, has_media/0, is_callback_query/0, callback_data_starts_with/1, matches/2, fallback/2, use_middleware/2, with_catch_handler/2, handle/3, merge/2, compose/2, compose_many/1, registered_commands/1, allowed_updates/1, scope/2, with_logging/1, with_filter/2, with_recovery/2, with_rate_limit/3]). -export_type([router/3, pattern/0, filter/0, route/3]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " # Telega Router\n" "\n" " The router module provides a flexible and composable routing system for Telegram bot updates.\n" " It allows you to define handlers for different types of messages and organize them into\n" " logical groups with middleware support, error handling, and composition capabilities.\n" "\n" " ## Basic Usage\n" "\n" " ```gleam\n" " import telega/router\n" " import telega/update\n" " import telega/reply\n" "\n" " let router =\n" " router.new(\"my_bot\")\n" " |> router.on_command(\"start\", handle_start)\n" " |> router.on_command(\"help\", handle_help)\n" " |> router.on_any_text(handle_text)\n" " |> router.on_photo(handle_photo)\n" " |> router.fallback(handle_unknown)\n" " ```\n" "\n" " ## Routing Priority\n" "\n" " Routes are matched in the following priority order:\n" " 1. **Commands** - Exact command matches (e.g., \"/start\", \"/help\")\n" " 2. **Callback Queries** - Callback data patterns\n" " 3. **Custom Routes** - User-defined matchers\n" " 4. **Media Routes** - Photo, video, voice, audio handlers\n" " 5. **Text Routes** - Text pattern matching\n" " 6. **Fallback** - Catch-all handler for unmatched updates\n" "\n" " Within each category, routes are tried in the order they were added,\n" " with the first matching route handling the update.\n" "\n" " ## Pattern Matching\n" "\n" " Text and callback queries support flexible pattern matching:\n" "\n" " ```gleam\n" " router\n" " |> router.on_text(Exact(\"hello\"), handle_hello)\n" " |> router.on_text(Prefix(\"search:\"), handle_search)\n" " |> router.on_text(Contains(\"help\"), handle_help_mention)\n" " |> router.on_text(Suffix(\"?\"), handle_question)\n" "\n" " router\n" " |> router.on_callback(Prefix(\"page:\"), handle_pagination)\n" " |> router.on_callback(Exact(\"cancel\"), handle_cancel)\n" " ```\n" "\n" " ## Middleware System\n" "\n" " Middleware allows you to wrap handlers with additional functionality.\n" " Middleware is applied in reverse order of addition (last added runs first):\n" "\n" " ```gleam\n" " router\n" " |> router.use_middleware(router.with_logging)\n" " |> router.use_middleware(auth_middleware)\n" " |> router.use_middleware(rate_limit_middleware)\n" " ```\n" "\n" " Built-in middleware includes:\n" " - `with_logging` - Logs all update processing\n" " - `with_filter` - Conditionally processes updates\n" " - `with_recovery` - Recovers from handler errors\n" "\n" " ## Error Handling\n" "\n" " Routers support catch handlers to gracefully handle errors from routes:\n" "\n" " ```gleam\n" " router\n" " |> router.with_catch_handler(fn(error) {\n" " log.error(\"Route error: \" <> string.inspect(error))\n" " Error(error)\n" " })\n" " ```\n" "\n" " The catch handler receives only the `error` (no context) and must return\n" " `Result(Context, error)` — log and re-raise with `Error(error)`, or recover\n" " with a context already in scope.\n" "\n" " Note: The router's catch handler only handles errors from route handlers.\n" " System-level errors (like session persistence failures) are handled by\n" " the bot's main catch handler configured via `telega.with_catch_handler`.\n" "\n" " ## Router Composition\n" "\n" " Routers can be composed to build complex routing structures:\n" "\n" " ### Merging Routers\n" "\n" " `merge` combines two routers into one, with all routes unified.\n" " Routes from the first router take priority in case of conflicts:\n" "\n" " ```gleam\n" " let admin_router =\n" " router.new(\"admin\")\n" " |> router.on_command(\"ban\", handle_ban)\n" " |> router.on_command(\"stats\", handle_stats)\n" "\n" " let user_router =\n" " router.new(\"user\")\n" " |> router.on_command(\"start\", handle_start)\n" " |> router.on_command(\"help\", handle_help)\n" "\n" " let main_router = router.merge(admin_router, user_router)\n" " ```\n" "\n" " ### Composing Routers\n" "\n" " `compose` creates a router that tries each sub-router in sequence.\n" " Each router maintains its own middleware and error handling:\n" "\n" " ```gleam\n" " let public_router =\n" " router.new(\"public\")\n" " |> router.use_middleware(rate_limiting)\n" " |> router.on_command(\"start\", handle_start)\n" "\n" " let private_router =\n" " router.new(\"private\")\n" " |> router.use_middleware(auth_required)\n" " |> router.on_command(\"admin\", handle_admin)\n" "\n" " let app = router.compose(private_router, public_router)\n" " ```\n" "\n" " ### Scoped Routing\n" "\n" " `scope` creates a sub-router that only processes updates matching a predicate:\n" "\n" " ```gleam\n" " let admin_router =\n" " router.new(\"admin\")\n" " |> router.on_command(\"ban\", handle_ban)\n" " |> router.scope(fn(update) {\n" " // Only process updates from admin users\n" " case update {\n" " update.CommandUpdate(from_id: id, ..) -> is_admin(id)\n" " _ -> False\n" " }\n" " })\n" " ```\n" "\n" " ## Custom Routes\n" "\n" " For complex routing logic, use custom matchers:\n" "\n" " ```gleam\n" " router\n" " |> router.on_custom(\n" " matcher: fn(update) {\n" " case update {\n" " update.TextUpdate(text: t, ..) ->\n" " string.starts_with(t, \"http://\") || string.starts_with(t, \"https://\")\n" " _ -> False\n" " }\n" " },\n" " handler: handle_link\n" " )\n" " ```\n" "\n" " ## Magic Filters\n" "\n" " The router includes a powerful filter system for creating complex routing conditions:\n" "\n" " ```gleam\n" " // Simple filters\n" " router\n" " |> router.on_filtered(router.is_private_chat(), handle_private)\n" " |> router.on_filtered(router.from_user(admin_id), handle_admin)\n" "\n" " // Combining filters with AND logic\n" " router\n" " |> router.on_filtered(\n" " router.and2(\n" " router.is_group_chat(),\n" " router.text_starts_with(\"!\")\n" " ),\n" " handle_group_command\n" " )\n" "\n" " // Combining multiple filters\n" " router\n" " |> router.on_filtered(\n" " router.and([\n" " router.is_text(),\n" " router.from_users([admin1, admin2, admin3]),\n" " router.not(router.text_starts_with(\"/\"))\n" " ]),\n" " handle_admin_text\n" " )\n" "\n" " // OR logic for multiple conditions\n" " router\n" " |> router.on_filtered(\n" " router.or([\n" " router.text_equals(\"help\"),\n" " router.text_equals(\"?\"),\n" " router.command_equals(\"help\")\n" " ]),\n" " show_help\n" " )\n" " ```\n" "\n" " ### Available Filters\n" "\n" " **Message Type Filters:**\n" " - `is_text()` - Text messages\n" " - `is_command()` - Command messages\n" " - `has_photo()` - Photo messages\n" " - `has_video()` - Video messages\n" " - `has_media()` - Any media (photo, video, audio, voice)\n" " - `is_media_group()` - Media group/album messages\n" " - `is_callback_query()` - Callback button presses\n" "\n" " **Text Content Filters:**\n" " - `text_equals(text)` - Exact text match\n" " - `text_starts_with(prefix)` - Text starts with prefix\n" " - `text_contains(substring)` - Text contains substring\n" " - `command_equals(cmd)` - Specific command\n" "\n" " **User/Chat Filters:**\n" " - `from_user(user_id)` - From specific user\n" " - `from_users(user_ids)` - From any of the users\n" " - `in_chat(chat_id)` - In specific chat\n" " - `is_private_chat()` - Private messages only\n" " - `is_group_chat()` - Group/supergroup messages only\n" "\n" " **Callback Query Filters:**\n" " - `callback_data_starts_with(prefix)` - Callback data prefix\n" "\n" " **Filter Composition:**\n" " - `and(filters)` / `and2(f1, f2)` - All filters must match\n" " - `or(filters)` / `or2(f1, f2)` - Any filter must match\n" " - `not(filter)` - Negate a filter\n" " - `filter(name, check_fn)` - Custom filter function\n" "\n" " ## Advanced Features\n" "\n" " ### Multiple Command Handlers\n" "\n" " Register the same handler for multiple commands:\n" "\n" " ```gleam\n" " router\n" " |> router.on_commands([\"start\", \"help\", \"about\"], show_info)\n" " ```\n" "\n" " ### Media Handling\n" "\n" " Handle different media types with dedicated handlers:\n" "\n" " ```gleam\n" " router\n" " |> router.on_photo(handle_photo)\n" " |> router.on_video(handle_video)\n" " |> router.on_voice(handle_voice_message)\n" " |> router.on_audio(handle_audio_file)\n" " |> router.on_media_group(handle_media_album)\n" " ```\n" "\n" " ### Handler Types\n" "\n" " The router provides type-safe handlers for different update types:\n" " - `CommandHandler` - Receives parsed command with arguments\n" " - `TextHandler` - Receives text string\n" " - `CallbackHandler` - Receives callback query ID and data\n" " - `PhotoHandler` - Receives list of photo sizes\n" " - `VideoHandler` - Receives video object\n" " - `VoiceHandler` - Receives voice message\n" " - `AudioHandler` - Receives audio file\n" " - `MediaGroupHandler` - Receives media group ID and list of messages\n" " - `Handler` - Generic handler for any update type\n" "\n" ). -opaque router(AJVG, AJVH, AJVI) :: {router, gleam@dict:dict(binary(), fun((telega@bot:context(AJVG, AJVH, AJVI), telega@update:update()) -> {ok, telega@bot:context(AJVG, AJVH, AJVI)} | {error, AJVH})), gleam@dict:dict(binary(), binary()), gleam@dict:dict(binary(), fun((telega@bot:context(AJVG, AJVH, AJVI), telega@update:update()) -> {ok, telega@bot:context(AJVG, AJVH, AJVI)} | {error, AJVH})), list(route(AJVG, AJVH, AJVI)), gleam@option:option(fun((telega@bot:context(AJVG, AJVH, AJVI), telega@update:update()) -> {ok, telega@bot:context(AJVG, AJVH, AJVI)} | {error, AJVH})), list(fun((fun((telega@bot:context(AJVG, AJVH, AJVI), telega@update:update()) -> {ok, telega@bot:context(AJVG, AJVH, AJVI)} | {error, AJVH})) -> fun((telega@bot:context(AJVG, AJVH, AJVI), telega@update:update()) -> {ok, telega@bot:context(AJVG, AJVH, AJVI)} | {error, AJVH}))), gleam@option:option(fun((AJVH) -> {ok, telega@bot:context(AJVG, AJVH, AJVI)} | {error, AJVH})), binary()} | {composed_router, list(router(AJVG, AJVH, AJVI)), binary()}. -type pattern() :: {exact, binary()} | {prefix, binary()} | {contains, binary()} | {suffix, binary()}. -opaque filter() :: {filter, fun((telega@update:update()) -> boolean()), binary()} | {'and', filter(), filter()} | {'or', filter(), filter()} | {'not', filter()}. -type route(AJVJ, AJVK, AJVL) :: {text_pattern_route, pattern(), fun((telega@bot:context(AJVJ, AJVK, AJVL), binary()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {photo_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), list(telega@model@types:photo_size())) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {video_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:video()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {voice_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:voice()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {audio_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:audio()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {media_group_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), binary(), list(telega@model@types:message())) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {inline_query_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:inline_query()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {chosen_inline_result_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:chosen_inline_result()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {shipping_query_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:shipping_query()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {pre_checkout_query_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:pre_checkout_query()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {poll_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:poll()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {poll_answer_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:poll_answer()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {message_reaction_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {message_reaction_emoji_route, list(binary()), fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {message_reaction_paid_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {message_reaction_added_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {message_reaction_removed_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {message_reaction_count_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:message_reaction_count_updated()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {chat_member_updated_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:chat_member_updated()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {chat_join_request_route, fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@model@types:chat_join_request()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {custom_route, fun((telega@update:update()) -> boolean()), fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@update:update()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})} | {filtered_route, filter(), fun((telega@bot:context(AJVJ, AJVK, AJVL), telega@update:update()) -> {ok, telega@bot:context(AJVJ, AJVK, AJVL)} | {error, AJVK})}. -file("src/telega/router.gleam", 484). ?DOC(" Create a new router\n"). -spec new(binary()) -> router(any(), any(), any()). new(Name) -> {router, maps:new(), maps:new(), maps:new(), [], none, [], none, Name}. -file("src/telega/router.gleam", 566). ?DOC(" Strip a single leading slash so command keys are stored consistently.\n"). -spec normalize_command(binary()) -> binary(). normalize_command(Command) -> case gleam_stdlib:string_starts_with(Command, <<"/"/utf8>>) of true -> gleam@string:drop_start(Command, 1); false -> Command end. -file("src/telega/router.gleam", 498). ?DOC(" Add a command handler\n"). -spec on_command( router(AKEP, AKEQ, AKER), binary(), fun((telega@bot:context(AKEP, AKEQ, AKER), telega@update:command()) -> {ok, telega@bot:context(AKEP, AKEQ, AKER)} | {error, AKEQ}) ) -> router(AKEP, AKEQ, AKER). on_command(Router, Command, Handler) -> case Router of {router, Commands, _, _, _, _, _, _, _} -> Command_key = normalize_command(Command), Wrapped_handler = fun(Ctx, Upd) -> case Upd of {command_update, _, _, Cmd, _, _} -> Handler(Ctx, Cmd); _ -> {ok, Ctx} end end, {router, gleam@dict:insert(Commands, Command_key, Wrapped_handler), erlang:element(3, Router), erlang:element(4, Router), erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 523). ?DOC(" Add multiple commands with same handler\n"). -spec on_commands( router(AKFB, AKFC, AKFD), list(binary()), fun((telega@bot:context(AKFB, AKFC, AKFD), telega@update:command()) -> {ok, telega@bot:context(AKFB, AKFC, AKFD)} | {error, AKFC}) ) -> router(AKFB, AKFC, AKFD). on_commands(Router, Commands, Handler) -> gleam@list:fold( Commands, Router, fun(R, Cmd) -> on_command(R, Cmd, Handler) end ). -file("src/telega/router.gleam", 544). ?DOC( " Add a command handler together with a human-readable description.\n" "\n" " The description is what shows up in the Telegram command menu. When the bot\n" " is started with `telega.with_auto_commands`, all commands registered this way\n" " are published via `setMyCommands` automatically, and `telega_i18n` can supply\n" " per-language variants. The description is ignored for routing — it only feeds\n" " command auto-synchronization.\n" "\n" " ```gleam\n" " router\n" " |> router.on_command_with_description(\"start\", \"Start the bot\", handle_start)\n" " |> router.on_command_with_description(\"help\", \"Show help\", handle_help)\n" " ```\n" ). -spec on_command_with_description( router(AKFO, AKFP, AKFQ), binary(), binary(), fun((telega@bot:context(AKFO, AKFP, AKFQ), telega@update:command()) -> {ok, telega@bot:context(AKFO, AKFP, AKFQ)} | {error, AKFP}) ) -> router(AKFO, AKFP, AKFQ). on_command_with_description(Router, Command, Description, Handler) -> Command_key = normalize_command(Command), case on_command(Router, Command, Handler) of {router, _, Command_descriptions, _, _, _, _, _, _} = Router@1 -> {router, erlang:element(2, Router@1), gleam@dict:insert( Command_descriptions, Command_key, Description ), erlang:element(4, Router@1), erlang:element(5, Router@1), erlang:element(6, Router@1), erlang:element(7, Router@1), erlang:element(8, Router@1), erlang:element(9, Router@1)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 574). ?DOC(" Add a text handler with pattern\n"). -spec on_text( router(AKGA, AKGB, AKGC), pattern(), fun((telega@bot:context(AKGA, AKGB, AKGC), binary()) -> {ok, telega@bot:context(AKGA, AKGB, AKGC)} | {error, AKGB}) ) -> router(AKGA, AKGB, AKGC). on_text(Router, Pattern, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{text_pattern_route, Pattern, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 587). ?DOC(" Add a handler for any text\n"). -spec on_any_text( router(AKGM, AKGN, AKGO), fun((telega@bot:context(AKGM, AKGN, AKGO), binary()) -> {ok, telega@bot:context(AKGM, AKGN, AKGO)} | {error, AKGN}) ) -> router(AKGM, AKGN, AKGO). on_any_text(Router, Handler) -> on_text(Router, {prefix, <<""/utf8>>}, Handler). -file("src/telega/router.gleam", 595). ?DOC(" Add a callback query handler with pattern\n"). -spec on_callback( router(AKGY, AKGZ, AKHA), pattern(), fun((telega@bot:context(AKGY, AKGZ, AKHA), binary(), binary()) -> {ok, telega@bot:context(AKGY, AKGZ, AKHA)} | {error, AKGZ}) ) -> router(AKGY, AKGZ, AKHA). on_callback(Router, Pattern, Handler) -> case Router of {router, _, _, Callbacks, _, _, _, _, _} -> Key = case Pattern of {exact, S} -> S; {prefix, S@1} -> <<"prefix:"/utf8, S@1/binary>>; {contains, S@2} -> <<"contains:"/utf8, S@2/binary>>; {suffix, S@3} -> <<"suffix:"/utf8, S@3/binary>> end, Wrapped_handler = fun(Ctx, Upd) -> case Upd of {callback_query_update, _, _, Query, _} -> case erlang:element(7, Query) of {some, Data} -> Handler(Ctx, erlang:element(2, Query), Data); none -> {ok, Ctx} end; _ -> {ok, Ctx} end end, {router, erlang:element(2, Router), erlang:element(3, Router), gleam@dict:insert(Callbacks, Key, Wrapped_handler), erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 626). ?DOC(" Add handlers for media types\n"). -spec on_photo( router(AKHK, AKHL, AKHM), fun((telega@bot:context(AKHK, AKHL, AKHM), list(telega@model@types:photo_size())) -> {ok, telega@bot:context(AKHK, AKHL, AKHM)} | {error, AKHL}) ) -> router(AKHK, AKHL, AKHM). on_photo(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{photo_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 637). -spec on_video( router(AKHW, AKHX, AKHY), fun((telega@bot:context(AKHW, AKHX, AKHY), telega@model@types:video()) -> {ok, telega@bot:context(AKHW, AKHX, AKHY)} | {error, AKHX}) ) -> router(AKHW, AKHX, AKHY). on_video(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{video_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 648). -spec on_voice( router(AKII, AKIJ, AKIK), fun((telega@bot:context(AKII, AKIJ, AKIK), telega@model@types:voice()) -> {ok, telega@bot:context(AKII, AKIJ, AKIK)} | {error, AKIJ}) ) -> router(AKII, AKIJ, AKIK). on_voice(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{voice_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 659). -spec on_audio( router(AKIU, AKIV, AKIW), fun((telega@bot:context(AKIU, AKIV, AKIW), telega@model@types:audio()) -> {ok, telega@bot:context(AKIU, AKIV, AKIW)} | {error, AKIV}) ) -> router(AKIU, AKIV, AKIW). on_audio(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{audio_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 671). ?DOC(" Add handler for media groups (albums of photos/videos)\n"). -spec on_media_group( router(AKJG, AKJH, AKJI), fun((telega@bot:context(AKJG, AKJH, AKJI), binary(), list(telega@model@types:message())) -> {ok, telega@bot:context(AKJG, AKJH, AKJI)} | {error, AKJH}) ) -> router(AKJG, AKJH, AKJI). on_media_group(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{media_group_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 683). ?DOC(" Add handler for inline queries\n"). -spec on_inline_query( router(AKJS, AKJT, AKJU), fun((telega@bot:context(AKJS, AKJT, AKJU), telega@model@types:inline_query()) -> {ok, telega@bot:context(AKJS, AKJT, AKJU)} | {error, AKJT}) ) -> router(AKJS, AKJT, AKJU). on_inline_query(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{inline_query_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 695). ?DOC(" Add handler for chosen inline results\n"). -spec on_chosen_inline_result( router(AKKE, AKKF, AKKG), fun((telega@bot:context(AKKE, AKKF, AKKG), telega@model@types:chosen_inline_result()) -> {ok, telega@bot:context(AKKE, AKKF, AKKG)} | {error, AKKF}) ) -> router(AKKE, AKKF, AKKG). on_chosen_inline_result(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{chosen_inline_result_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 707). ?DOC(" Add handler for shipping queries (payments)\n"). -spec on_shipping_query( router(AKKQ, AKKR, AKKS), fun((telega@bot:context(AKKQ, AKKR, AKKS), telega@model@types:shipping_query()) -> {ok, telega@bot:context(AKKQ, AKKR, AKKS)} | {error, AKKR}) ) -> router(AKKQ, AKKR, AKKS). on_shipping_query(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{shipping_query_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 719). ?DOC(" Add handler for pre-checkout queries (payments)\n"). -spec on_pre_checkout_query( router(AKLC, AKLD, AKLE), fun((telega@bot:context(AKLC, AKLD, AKLE), telega@model@types:pre_checkout_query()) -> {ok, telega@bot:context(AKLC, AKLD, AKLE)} | {error, AKLD}) ) -> router(AKLC, AKLD, AKLE). on_pre_checkout_query(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{pre_checkout_query_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 731). ?DOC(" Add handler for poll updates\n"). -spec on_poll( router(AKLO, AKLP, AKLQ), fun((telega@bot:context(AKLO, AKLP, AKLQ), telega@model@types:poll()) -> {ok, telega@bot:context(AKLO, AKLP, AKLQ)} | {error, AKLP}) ) -> router(AKLO, AKLP, AKLQ). on_poll(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{poll_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 743). ?DOC(" Add handler for poll answer updates\n"). -spec on_poll_answer( router(AKMA, AKMB, AKMC), fun((telega@bot:context(AKMA, AKMB, AKMC), telega@model@types:poll_answer()) -> {ok, telega@bot:context(AKMA, AKMB, AKMC)} | {error, AKMB}) ) -> router(AKMA, AKMB, AKMC). on_poll_answer(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{poll_answer_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 755). ?DOC(" Add handler for message reactions\n"). -spec on_reaction( router(AKMM, AKMN, AKMO), fun((telega@bot:context(AKMM, AKMN, AKMO), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AKMM, AKMN, AKMO)} | {error, AKMN}) ) -> router(AKMM, AKMN, AKMO). on_reaction(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{message_reaction_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 773). ?DOC( " Add handler for a specific emoji reaction\n" "\n" " ## Example\n" " ```gleam\n" " router\n" " |> router.on_reaction_emoji(\"👍\", handle_like)\n" " ```\n" ). -spec on_reaction_emoji( router(AKMY, AKMZ, AKNA), binary(), fun((telega@bot:context(AKMY, AKMZ, AKNA), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AKMY, AKMZ, AKNA)} | {error, AKMZ}) ) -> router(AKMY, AKMZ, AKNA). on_reaction_emoji(Router, Emoji, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{message_reaction_emoji_route, [Emoji], Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 795). ?DOC( " Add handler for multiple emoji reactions\n" "\n" " ## Example\n" " ```gleam\n" " router\n" " |> router.on_reaction_emojis([\"👍\", \"❤\", \"🔥\"], handle_positive_reactions)\n" " ```\n" ). -spec on_reaction_emojis( router(AKNK, AKNL, AKNM), list(binary()), fun((telega@bot:context(AKNK, AKNL, AKNM), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AKNK, AKNL, AKNM)} | {error, AKNL}) ) -> router(AKNK, AKNL, AKNM). on_reaction_emojis(Router, Emojis, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{message_reaction_emoji_route, Emojis, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 817). ?DOC( " Add handler for paid reactions (stars)\n" "\n" " ## Example\n" " ```gleam\n" " router\n" " |> router.on_paid_reaction(handle_star_reaction)\n" " ```\n" ). -spec on_paid_reaction( router(AKNX, AKNY, AKNZ), fun((telega@bot:context(AKNX, AKNY, AKNZ), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AKNX, AKNY, AKNZ)} | {error, AKNY}) ) -> router(AKNX, AKNY, AKNZ). on_paid_reaction(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{message_reaction_paid_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 835). ?DOC( " Add handler for added reactions only (filters out removed reactions)\n" "\n" " ## Example\n" " ```gleam\n" " router\n" " |> router.on_reaction_added(handle_new_reaction)\n" " ```\n" ). -spec on_reaction_added( router(AKOJ, AKOK, AKOL), fun((telega@bot:context(AKOJ, AKOK, AKOL), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AKOJ, AKOK, AKOL)} | {error, AKOK}) ) -> router(AKOJ, AKOK, AKOL). on_reaction_added(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{message_reaction_added_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 853). ?DOC( " Add handler for removed reactions only (filters out added reactions)\n" "\n" " ## Example\n" " ```gleam\n" " router\n" " |> router.on_reaction_removed(handle_removed_reaction)\n" " ```\n" ). -spec on_reaction_removed( router(AKOV, AKOW, AKOX), fun((telega@bot:context(AKOV, AKOW, AKOX), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AKOV, AKOW, AKOX)} | {error, AKOW}) ) -> router(AKOV, AKOW, AKOX). on_reaction_removed(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{message_reaction_removed_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 871). ?DOC( " Add handler for message reaction count updates (anonymous reactions in channels)\n" "\n" " ## Example\n" " ```gleam\n" " router\n" " |> router.on_reaction_count(handle_reaction_counts)\n" " ```\n" ). -spec on_reaction_count( router(AKPH, AKPI, AKPJ), fun((telega@bot:context(AKPH, AKPI, AKPJ), telega@model@types:message_reaction_count_updated()) -> {ok, telega@bot:context(AKPH, AKPI, AKPJ)} | {error, AKPI}) ) -> router(AKPH, AKPI, AKPJ). on_reaction_count(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{message_reaction_count_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 883). ?DOC(" Add handler for chat member updates\n"). -spec on_chat_member_updated( router(AKPT, AKPU, AKPV), fun((telega@bot:context(AKPT, AKPU, AKPV), telega@model@types:chat_member_updated()) -> {ok, telega@bot:context(AKPT, AKPU, AKPV)} | {error, AKPU}) ) -> router(AKPT, AKPU, AKPV). on_chat_member_updated(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{chat_member_updated_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 895). ?DOC(" Add handler for chat join requests\n"). -spec on_chat_join_request( router(AKQF, AKQG, AKQH), fun((telega@bot:context(AKQF, AKQG, AKQH), telega@model@types:chat_join_request()) -> {ok, telega@bot:context(AKQF, AKQG, AKQH)} | {error, AKQG}) ) -> router(AKQF, AKQG, AKQH). on_chat_join_request(Router, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{chat_join_request_route, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 907). ?DOC(" Add a custom route with matcher function\n"). -spec on_custom( router(AKQR, AKQS, AKQT), fun((telega@update:update()) -> boolean()), fun((telega@bot:context(AKQR, AKQS, AKQT), telega@update:update()) -> {ok, telega@bot:context(AKQR, AKQS, AKQT)} | {error, AKQS}) ) -> router(AKQR, AKQS, AKQT). on_custom(Router, Matcher, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{custom_route, Matcher, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 920). ?DOC(" Add a filtered route\n"). -spec on_filtered( router(AKRD, AKRE, AKRF), filter(), fun((telega@bot:context(AKRD, AKRE, AKRF), telega@update:update()) -> {ok, telega@bot:context(AKRD, AKRE, AKRF)} | {error, AKRE}) ) -> router(AKRD, AKRE, AKRF). on_filtered(Router, Filter, Handler) -> case Router of {router, _, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), [{filtered_route, Filter, Handler} | Routes], erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 933). ?DOC(" Create a filter from a custom function\n"). -spec filter(binary(), fun((telega@update:update()) -> boolean())) -> filter(). filter(Name, Check) -> {filter, Check, Name}. -file("src/telega/router.gleam", 938). ?DOC(" Combine filters with AND logic\n"). -spec 'and'(list(filter())) -> filter(). 'and'(Filters) -> case Filters of [] -> filter(<<"always"/utf8>>, fun(_) -> true end); [F] -> F; [F1, F2] -> {'and', F1, F2}; [F1@1 | Rest] -> {'and', F1@1, 'and'(Rest)} end. -file("src/telega/router.gleam", 948). ?DOC(" Combine two filters with AND logic\n"). -spec and2(filter(), filter()) -> filter(). and2(Left, Right) -> {'and', Left, Right}. -file("src/telega/router.gleam", 953). ?DOC(" Combine filters with OR logic\n"). -spec 'or'(list(filter())) -> filter(). 'or'(Filters) -> case Filters of [] -> filter(<<"never"/utf8>>, fun(_) -> false end); [F] -> F; [F1, F2] -> {'or', F1, F2}; [F1@1 | Rest] -> {'or', F1@1, 'or'(Rest)} end. -file("src/telega/router.gleam", 963). ?DOC(" Combine two filters with OR logic\n"). -spec or2(filter(), filter()) -> filter(). or2(Left, Right) -> {'or', Left, Right}. -file("src/telega/router.gleam", 968). ?DOC(" Negate a filter\n"). -spec 'not'(filter()) -> filter(). 'not'(F) -> {'not', F}. -file("src/telega/router.gleam", 973). ?DOC(" Filter for text messages\n"). -spec is_text() -> filter(). is_text() -> filter(<<"is_text"/utf8>>, fun(Update) -> case Update of {text_update, _, _, _, _, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 983). ?DOC(" Filter for text that equals a specific value\n"). -spec text_equals(binary()) -> filter(). text_equals(Text) -> filter(<<"text_equals:"/utf8, Text/binary>>, fun(Update) -> case Update of {text_update, _, _, T, _, _} -> T =:= Text; _ -> false end end). -file("src/telega/router.gleam", 993). ?DOC(" Filter for text that starts with a prefix\n"). -spec text_starts_with(binary()) -> filter(). text_starts_with(Prefix) -> filter( <<"text_starts_with:"/utf8, Prefix/binary>>, fun(Update) -> case Update of {text_update, _, _, T, _, _} -> gleam_stdlib:string_starts_with(T, Prefix); _ -> false end end ). -file("src/telega/router.gleam", 1003). ?DOC(" Filter for text that contains a substring\n"). -spec text_contains(binary()) -> filter(). text_contains(Substring) -> filter( <<"text_contains:"/utf8, Substring/binary>>, fun(Update) -> case Update of {text_update, _, _, T, _, _} -> gleam_stdlib:contains_string(T, Substring); _ -> false end end ). -file("src/telega/router.gleam", 1013). ?DOC(" Filter for commands\n"). -spec is_command() -> filter(). is_command() -> filter(<<"is_command"/utf8>>, fun(Update) -> case Update of {command_update, _, _, _, _, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 1023). ?DOC(" Filter for specific command\n"). -spec command_equals(binary()) -> filter(). command_equals(Cmd) -> filter(<<"command:"/utf8, Cmd/binary>>, fun(Update) -> case Update of {command_update, _, _, Command, _, _} -> erlang:element(3, Command) =:= Cmd; _ -> false end end). -file("src/telega/router.gleam", 1033). ?DOC(" Filter by user ID\n"). -spec from_user(integer()) -> filter(). from_user(User_id) -> filter( <<"from_user:"/utf8, (gleam@string:inspect(User_id))/binary>>, fun(Update) -> case Update of {text_update, From_id, _, _, _, _} -> From_id =:= User_id; {command_update, From_id@1, _, _, _, _} -> From_id@1 =:= User_id; {callback_query_update, From_id@2, _, _, _} -> From_id@2 =:= User_id; {photo_update, From_id@3, _, _, _, _} -> From_id@3 =:= User_id; {video_update, From_id@4, _, _, _, _} -> From_id@4 =:= User_id; {voice_update, From_id@5, _, _, _, _} -> From_id@5 =:= User_id; {audio_update, From_id@6, _, _, _, _} -> From_id@6 =:= User_id; _ -> false end end ). -file("src/telega/router.gleam", 1049). ?DOC(" Filter by multiple user IDs\n"). -spec from_users(list(integer())) -> filter(). from_users(User_ids) -> filter(<<"from_users"/utf8>>, fun(Update) -> case Update of {text_update, From_id, _, _, _, _} -> gleam@list:contains(User_ids, From_id); {command_update, From_id@1, _, _, _, _} -> gleam@list:contains(User_ids, From_id@1); {callback_query_update, From_id@2, _, _, _} -> gleam@list:contains(User_ids, From_id@2); {photo_update, From_id@3, _, _, _, _} -> gleam@list:contains(User_ids, From_id@3); {video_update, From_id@4, _, _, _, _} -> gleam@list:contains(User_ids, From_id@4); {voice_update, From_id@5, _, _, _, _} -> gleam@list:contains(User_ids, From_id@5); {audio_update, From_id@6, _, _, _, _} -> gleam@list:contains(User_ids, From_id@6); _ -> false end end). -file("src/telega/router.gleam", 1066). ?DOC(" Filter by chat ID\n"). -spec in_chat(integer()) -> filter(). in_chat(Chat_id) -> filter( <<"in_chat:"/utf8, (gleam@string:inspect(Chat_id))/binary>>, fun(Update) -> erlang:element(3, Update) =:= Chat_id end ). -file("src/telega/router.gleam", 1082). ?DOC( " Filter by multiple chat IDs. Matches when the update's chat is one of\n" " `chat_ids` — a whitelist of chats. Combine with `not` for a blacklist:\n" "\n" " ```gleam\n" " // Only react in the support chats\n" " router.on_filtered(router.from_chats([-100_1, -100_2]), handler)\n" "\n" " // React everywhere except the banned chats\n" " router.on_filtered(router.not(router.from_chats([-100_666])), handler)\n" " ```\n" ). -spec from_chats(list(integer())) -> filter(). from_chats(Chat_ids) -> filter( <<"from_chats"/utf8>>, fun(Update) -> gleam@list:contains(Chat_ids, erlang:element(3, Update)) end ). -file("src/telega/router.gleam", 1088). ?DOC( " Filter for private chats\n" " https://core.telegram.org/api/bots%2Fids#user-ids\n" ). -spec is_private_chat() -> filter(). is_private_chat() -> filter( <<"is_private_chat"/utf8>>, fun(Update) -> erlang:element(3, Update) > 0 end ). -file("src/telega/router.gleam", 1094). ?DOC( " Filter for group chats\n" " https://core.telegram.org/api/bots%2Fids#supergroup-channel-ids\n" ). -spec is_group_chat() -> filter(). is_group_chat() -> filter( <<"is_group_chat"/utf8>>, fun(Update) -> erlang:element(3, Update) < 0 end ). -file("src/telega/router.gleam", 1099). ?DOC(" Filter for photo messages\n"). -spec has_photo() -> filter(). has_photo() -> filter(<<"has_photo"/utf8>>, fun(Update) -> case Update of {photo_update, _, _, _, _, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 1109). ?DOC(" Filter for video messages\n"). -spec has_video() -> filter(). has_video() -> filter(<<"has_video"/utf8>>, fun(Update) -> case Update of {video_update, _, _, _, _, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 1119). ?DOC(" Filter for media group messages\n"). -spec is_media_group() -> filter(). is_media_group() -> filter(<<"is_media_group"/utf8>>, fun(Update) -> case Update of {media_group_update, _, _, _, _, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 1129). ?DOC(" Filter for media (photo, video, audio, voice)\n"). -spec has_media() -> filter(). has_media() -> filter(<<"has_media"/utf8>>, fun(Update) -> case Update of {photo_update, _, _, _, _, _} -> true; {video_update, _, _, _, _, _} -> true; {audio_update, _, _, _, _, _} -> true; {voice_update, _, _, _, _, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 1142). ?DOC(" Filter for callback queries\n"). -spec is_callback_query() -> filter(). is_callback_query() -> filter(<<"is_callback_query"/utf8>>, fun(Update) -> case Update of {callback_query_update, _, _, _, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 1152). ?DOC(" Filter for callback data that starts with prefix\n"). -spec callback_data_starts_with(binary()) -> filter(). callback_data_starts_with(Prefix) -> filter( <<"callback_data_starts_with:"/utf8, Prefix/binary>>, fun(Update) -> case Update of {callback_query_update, _, _, Query, _} -> case erlang:element(7, Query) of {some, Data} -> gleam_stdlib:string_starts_with(Data, Prefix); none -> false end; _ -> false end end ). -file("src/telega/router.gleam", 1184). ?DOC(" Evaluate a filter against an update\n"). -spec evaluate_filter(filter(), telega@update:update()) -> boolean(). evaluate_filter(F, Update) -> case F of {filter, Check, _} -> Check(Update); {'and', Left, Right} -> evaluate_filter(Left, Update) andalso evaluate_filter(Right, Update); {'or', Left@1, Right@1} -> evaluate_filter(Left@1, Update) orelse evaluate_filter( Right@1, Update ); {'not', Filter} -> not evaluate_filter(Filter, Update) end. -file("src/telega/router.gleam", 1179). ?DOC( " Evaluate a composable `Filter` against an update.\n" "\n" " This is the bridge that lets the filter combinators (`and`/`or`/`not`,\n" " `is_text`, `has_photo`, …) be reused outside the router — most notably to\n" " drive `telega.wait_filtered` / `telega.wait_for` in conversations:\n" "\n" " ```gleam\n" " use ctx, upd <- telega.wait_for(\n" " ctx,\n" " filter: router.matches(router.or2(router.is_text(), router.has_photo()), _),\n" " or: None,\n" " timeout: None,\n" " )\n" " ```\n" ). -spec matches(filter(), telega@update:update()) -> boolean(). matches(Filter, Update) -> evaluate_filter(Filter, Update). -file("src/telega/router.gleam", 1196). ?DOC(" Set fallback handler for unmatched updates\n"). -spec fallback( router(AKRT, AKRU, AKRV), fun((telega@bot:context(AKRT, AKRU, AKRV), telega@update:update()) -> {ok, telega@bot:context(AKRT, AKRU, AKRV)} | {error, AKRU}) ) -> router(AKRT, AKRU, AKRV). fallback(Router, Handler) -> case Router of {router, _, _, _, _, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), erlang:element(5, Router), {some, Handler}, erlang:element(7, Router), erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 1207). ?DOC(" Add middleware to the router\n"). -spec use_middleware( router(AKSF, AKSG, AKSH), fun((fun((telega@bot:context(AKSF, AKSG, AKSH), telega@update:update()) -> {ok, telega@bot:context(AKSF, AKSG, AKSH)} | {error, AKSG})) -> fun((telega@bot:context(AKSF, AKSG, AKSH), telega@update:update()) -> {ok, telega@bot:context(AKSF, AKSG, AKSH)} | {error, AKSG})) ) -> router(AKSF, AKSG, AKSH). use_middleware(Router, Middleware) -> case Router of {router, _, _, _, _, _, Existing_middleware, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), erlang:element(5, Router), erlang:element(6, Router), [Middleware | Existing_middleware], erlang:element(8, Router), erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 1219). ?DOC(" Add a catch handler to the router that handles errors from all routes\n"). -spec with_catch_handler( router(AKSR, AKSS, AKST), fun((AKSS) -> {ok, telega@bot:context(AKSR, AKSS, AKST)} | {error, AKSS}) ) -> router(AKSR, AKSS, AKST). with_catch_handler(Router, Catch_handler) -> case Router of {router, _, _, _, _, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), {some, Catch_handler}, erlang:element(9, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 2152). ?DOC(" Check if text matches pattern\n"). -spec matches_pattern(pattern(), binary()) -> boolean(). matches_pattern(Pattern, Text) -> case Pattern of {exact, P} -> Text =:= P; {prefix, P@1} -> gleam_stdlib:string_starts_with(Text, P@1); {contains, P@2} -> gleam_stdlib:contains_string(Text, P@2); {suffix, P@3} -> gleam_stdlib:string_ends_with(Text, P@3) end. -file("src/telega/router.gleam", 1562). ?DOC(" Check if a router can handle a given update (has specific routes or fallback)\n"). -spec can_handle_update(router(any(), any(), any()), telega@update:update()) -> boolean(). can_handle_update(Router, Update) -> case Router of {router, Commands, _, Callbacks, Routes, Fallback, _, _, _} -> Has_specific_route = case Update of {command_update, _, _, Cmd, _, _} -> gleam@dict:has_key(Commands, erlang:element(3, Cmd)); {text_update, _, _, Text, _, _} -> gleam@list:any(Routes, fun(Route) -> case Route of {text_pattern_route, Pattern, _} -> matches_pattern(Pattern, Text); _ -> false end end); {callback_query_update, _, _, Query, _} -> case erlang:element(7, Query) of {some, Data} -> gleam@dict:has_key(Callbacks, Data) orelse begin _pipe = maps:to_list(Callbacks), gleam@list:any( _pipe, fun(Entry) -> {Key, _} = Entry, case gleam@string:split( Key, <<":"/utf8>> ) of [<<"prefix"/utf8>>, Prefix] -> gleam_stdlib:string_starts_with( Data, Prefix ); [<<"contains"/utf8>>, Substr] -> gleam_stdlib:contains_string( Data, Substr ); [<<"suffix"/utf8>>, Suffix] -> gleam_stdlib:string_ends_with( Data, Suffix ); _ -> Key =:= Data end end ) end; none -> false end; {photo_update, _, _, _, _, _} -> gleam@list:any(Routes, fun(Route@1) -> case Route@1 of {photo_route, _} -> true; _ -> false end end); {video_update, _, _, _, _, _} -> gleam@list:any(Routes, fun(Route@2) -> case Route@2 of {video_route, _} -> true; _ -> false end end); {voice_update, _, _, _, _, _} -> gleam@list:any(Routes, fun(Route@3) -> case Route@3 of {voice_route, _} -> true; _ -> false end end); {audio_update, _, _, _, _, _} -> gleam@list:any(Routes, fun(Route@4) -> case Route@4 of {audio_route, _} -> true; _ -> false end end); {media_group_update, _, _, _, _, _} -> gleam@list:any(Routes, fun(Route@5) -> case Route@5 of {media_group_route, _} -> true; _ -> false end end); _ -> false end, (Has_specific_route orelse gleam@list:any( Routes, fun(Route@6) -> case Route@6 of {custom_route, Matcher, _} -> Matcher(Update); {filtered_route, Filter, _} -> evaluate_filter(Filter, Update); _ -> false end end )) orelse gleam@option:is_some(Fallback); {composed_router, Composes, _} -> gleam@list:any(Composes, fun(R) -> can_handle_update(R, Update) end) end. -file("src/telega/router.gleam", 2217). ?DOC(" Apply middleware to a handler\n"). -spec apply_middleware( fun((telega@bot:context(ALCZ, ALDA, ALDB), telega@update:update()) -> {ok, telega@bot:context(ALCZ, ALDA, ALDB)} | {error, ALDA}), list(fun((fun((telega@bot:context(ALCZ, ALDA, ALDB), telega@update:update()) -> {ok, telega@bot:context(ALCZ, ALDA, ALDB)} | {error, ALDA})) -> fun((telega@bot:context(ALCZ, ALDA, ALDB), telega@update:update()) -> {ok, telega@bot:context(ALCZ, ALDA, ALDB)} | {error, ALDA}))) ) -> fun((telega@bot:context(ALCZ, ALDA, ALDB), telega@update:update()) -> {ok, telega@bot:context(ALCZ, ALDA, ALDB)} | {error, ALDA}). apply_middleware(Handler, Middleware) -> gleam@list:fold(Middleware, Handler, fun(H, Mw) -> Mw(H) end). -file("src/telega/router.gleam", 2203). ?DOC(" Check if two reaction types are equal\n"). -spec reaction_type_equals( telega@model@types:reaction_type(), telega@model@types:reaction_type() ) -> boolean(). reaction_type_equals(A, B) -> case {A, B} of {{reaction_type_emoji_reaction_type, A_inner}, {reaction_type_emoji_reaction_type, B_inner}} -> erlang:element(3, A_inner) =:= erlang:element(3, B_inner); {{reaction_type_custom_emoji_reaction_type, A_inner@1}, {reaction_type_custom_emoji_reaction_type, B_inner@1}} -> erlang:element(3, A_inner@1) =:= erlang:element(3, B_inner@1); {{reaction_type_paid_reaction_type, _}, {reaction_type_paid_reaction_type, _}} -> true; {_, _} -> false end. -file("src/telega/router.gleam", 2194). ?DOC(" Check if there are removed reactions (old_reaction has items not in new_reaction)\n"). -spec has_removed_reactions(telega@model@types:message_reaction_updated()) -> boolean(). has_removed_reactions(Update) -> gleam@list:any( erlang:element(7, Update), fun(Old_r) -> not gleam@list:any( erlang:element(8, Update), fun(New_r) -> reaction_type_equals(Old_r, New_r) end ) end ). -file("src/telega/router.gleam", 2185). ?DOC(" Check if there are added reactions (new_reaction has items not in old_reaction)\n"). -spec has_added_reactions(telega@model@types:message_reaction_updated()) -> boolean(). has_added_reactions(Update) -> gleam@list:any( erlang:element(8, Update), fun(New_r) -> not gleam@list:any( erlang:element(7, Update), fun(Old_r) -> reaction_type_equals(New_r, Old_r) end ) end ). -file("src/telega/router.gleam", 2175). ?DOC(" Check if any reaction is a paid reaction\n"). -spec has_paid_reaction(list(telega@model@types:reaction_type())) -> boolean(). has_paid_reaction(Reactions) -> gleam@list:any(Reactions, fun(Reaction) -> case Reaction of {reaction_type_paid_reaction_type, _} -> true; _ -> false end end). -file("src/telega/router.gleam", 2162). ?DOC(" Check if any reaction matches the specified emojis\n"). -spec matches_reaction_emojis( list(telega@model@types:reaction_type()), list(binary()) ) -> boolean(). matches_reaction_emojis(Reactions, Emojis) -> gleam@list:any(Reactions, fun(Reaction) -> case Reaction of {reaction_type_emoji_reaction_type, Inner} -> gleam@list:contains(Emojis, erlang:element(3, Inner)); _ -> false end end). -file("src/telega/router.gleam", 2111). ?DOC(" Check if a route matches an update\n"). -spec route_matches(route(any(), any(), any()), telega@update:update()) -> boolean(). route_matches(Route, Update) -> case {Route, Update} of {{text_pattern_route, Pattern, _}, {text_update, _, _, Text, _, _}} -> matches_pattern(Pattern, Text); {{photo_route, _}, {photo_update, _, _, _, _, _}} -> true; {{video_route, _}, {video_update, _, _, _, _, _}} -> true; {{voice_route, _}, {voice_update, _, _, _, _, _}} -> true; {{audio_route, _}, {audio_update, _, _, _, _, _}} -> true; {{media_group_route, _}, {media_group_update, _, _, _, _, _}} -> true; {{inline_query_route, _}, {inline_query_update, _, _, _, _}} -> true; {{chosen_inline_result_route, _}, {chosen_inline_result_update, _, _, _, _}} -> true; {{shipping_query_route, _}, {shipping_query_update, _, _, _, _}} -> true; {{pre_checkout_query_route, _}, {pre_checkout_query_update, _, _, _, _}} -> true; {{poll_route, _}, {poll_update, _, _, _, _}} -> true; {{poll_answer_route, _}, {poll_answer_update, _, _, _, _}} -> true; {{message_reaction_route, _}, {message_reaction_update, _, _, _, _}} -> true; {{message_reaction_emoji_route, Emojis, _}, {message_reaction_update, _, _, Message_reaction_updated, _}} -> matches_reaction_emojis( erlang:element(8, Message_reaction_updated), Emojis ); {{message_reaction_paid_route, _}, {message_reaction_update, _, _, Message_reaction_updated@1, _}} -> has_paid_reaction(erlang:element(8, Message_reaction_updated@1)); {{message_reaction_added_route, _}, {message_reaction_update, _, _, Message_reaction_updated@2, _}} -> has_added_reactions(Message_reaction_updated@2); {{message_reaction_removed_route, _}, {message_reaction_update, _, _, Message_reaction_updated@3, _}} -> has_removed_reactions(Message_reaction_updated@3); {{message_reaction_count_route, _}, {message_reaction_count_update, _, _, _, _}} -> true; {{chat_member_updated_route, _}, {chat_member_update, _, _, _, _}} -> true; {{chat_join_request_route, _}, {chat_join_request_update, _, _, _, _}} -> true; {{custom_route, Matcher, _}, _} -> Matcher(Update); {{filtered_route, Filter, _}, _} -> evaluate_filter(Filter, Update); {_, _} -> false end. -file("src/telega/router.gleam", 2024). ?DOC(" Find matching route for an update\n"). -spec find_matching_route(list(route(ALCF, ALCG, ALCH)), telega@update:update()) -> gleam@option:option(fun((telega@bot:context(ALCF, ALCG, ALCH), telega@update:update()) -> {ok, telega@bot:context(ALCF, ALCG, ALCH)} | {error, ALCG})). find_matching_route(Routes, Update) -> case Routes of [] -> none; [Route | Rest] -> case route_matches(Route, Update) of true -> Handler@22 = case {Route, Update} of {{text_pattern_route, _, Handler}, {text_update, _, _, Text, _, _}} -> fun(Ctx, _) -> Handler(Ctx, Text) end; {{photo_route, Handler@1}, {photo_update, _, _, Photos, _, _}} -> fun(Ctx@1, _) -> Handler@1(Ctx@1, Photos) end; {{video_route, Handler@2}, {video_update, _, _, Video, _, _}} -> fun(Ctx@2, _) -> Handler@2(Ctx@2, Video) end; {{voice_route, Handler@3}, {voice_update, _, _, Voice, _, _}} -> fun(Ctx@3, _) -> Handler@3(Ctx@3, Voice) end; {{audio_route, Handler@4}, {audio_update, _, _, Audio, _, _}} -> fun(Ctx@4, _) -> Handler@4(Ctx@4, Audio) end; {{media_group_route, Handler@5}, {media_group_update, _, _, Media_group_id, Messages, _}} -> fun(Ctx@5, _) -> Handler@5(Ctx@5, Media_group_id, Messages) end; {{inline_query_route, Handler@6}, {inline_query_update, _, _, Inline_query, _}} -> fun(Ctx@6, _) -> Handler@6(Ctx@6, Inline_query) end; {{chosen_inline_result_route, Handler@7}, {chosen_inline_result_update, _, _, Chosen_inline_result, _}} -> fun(Ctx@7, _) -> Handler@7(Ctx@7, Chosen_inline_result) end; {{shipping_query_route, Handler@8}, {shipping_query_update, _, _, Shipping_query, _}} -> fun(Ctx@8, _) -> Handler@8(Ctx@8, Shipping_query) end; {{pre_checkout_query_route, Handler@9}, {pre_checkout_query_update, _, _, Pre_checkout_query, _}} -> fun(Ctx@9, _) -> Handler@9(Ctx@9, Pre_checkout_query) end; {{poll_route, Handler@10}, {poll_update, _, _, Poll, _}} -> fun(Ctx@10, _) -> Handler@10(Ctx@10, Poll) end; {{poll_answer_route, Handler@11}, {poll_answer_update, _, _, Poll_answer, _}} -> fun(Ctx@11, _) -> Handler@11(Ctx@11, Poll_answer) end; {{message_reaction_route, Handler@12}, {message_reaction_update, _, _, Message_reaction_updated, _}} -> fun(Ctx@12, _) -> Handler@12(Ctx@12, Message_reaction_updated) end; {{message_reaction_emoji_route, _, Handler@13}, {message_reaction_update, _, _, Message_reaction_updated@1, _}} -> fun(Ctx@13, _) -> Handler@13(Ctx@13, Message_reaction_updated@1) end; {{message_reaction_paid_route, Handler@14}, {message_reaction_update, _, _, Message_reaction_updated@2, _}} -> fun(Ctx@14, _) -> Handler@14(Ctx@14, Message_reaction_updated@2) end; {{message_reaction_added_route, Handler@15}, {message_reaction_update, _, _, Message_reaction_updated@3, _}} -> fun(Ctx@15, _) -> Handler@15(Ctx@15, Message_reaction_updated@3) end; {{message_reaction_removed_route, Handler@16}, {message_reaction_update, _, _, Message_reaction_updated@4, _}} -> fun(Ctx@16, _) -> Handler@16(Ctx@16, Message_reaction_updated@4) end; {{message_reaction_count_route, Handler@17}, {message_reaction_count_update, _, _, Message_reaction_count_updated, _}} -> fun(Ctx@17, _) -> Handler@17( Ctx@17, Message_reaction_count_updated ) end; {{chat_member_updated_route, Handler@18}, {chat_member_update, _, _, Chat_member_updated, _}} -> fun(Ctx@18, _) -> Handler@18(Ctx@18, Chat_member_updated) end; {{chat_join_request_route, Handler@19}, {chat_join_request_update, _, _, Chat_join_request, _}} -> fun(Ctx@19, _) -> Handler@19(Ctx@19, Chat_join_request) end; {{custom_route, _, Handler@20}, _} -> Handler@20; {{filtered_route, _, Handler@21}, _} -> Handler@21; {_, _} -> fun(Ctx@20, _) -> {ok, Ctx@20} end end, {some, Handler@22}; false -> find_matching_route(Rest, Update) end end. -file("src/telega/router.gleam", 2008). ?DOC(" Find route or fallback in composed routers\n"). -spec find_route_or_fallback_in_composed( list(router(ALBV, ALBW, ALBX)), telega@update:update() ) -> fun((telega@bot:context(ALBV, ALBW, ALBX), telega@update:update()) -> {ok, telega@bot:context(ALBV, ALBW, ALBX)} | {error, ALBW}). find_route_or_fallback_in_composed(Routers, Update) -> case Routers of [] -> fun(Ctx, _) -> {ok, Ctx} end; [Router | Rest] -> case can_handle_update(Router, Update) of true -> find_route_or_fallback(Router, Update); false -> find_route_or_fallback_in_composed(Rest, Update) end end. -file("src/telega/router.gleam", 1987). ?DOC(" Try routes, then fallback\n"). -spec find_route_or_fallback(router(ALBM, ALBN, ALBO), telega@update:update()) -> fun((telega@bot:context(ALBM, ALBN, ALBO), telega@update:update()) -> {ok, telega@bot:context(ALBM, ALBN, ALBO)} | {error, ALBN}). find_route_or_fallback(Router, Update) -> case Router of {router, _, _, _, Routes, Fallback, _, _, _} -> case find_matching_route(Routes, Update) of {some, Handler} -> Handler; none -> case Fallback of {some, Handler@1} -> Handler@1; none -> fun(Ctx, _) -> {ok, Ctx} end end end; {composed_router, Composes, _} -> find_route_or_fallback_in_composed(Composes, Update) end. -file("src/telega/router.gleam", 1977). ?DOC( " Check if a callback pattern key matches the data. The pattern payload\n" " may itself contain \":\" (e.g. `Prefix(\"travel_to:\")`), so split only on\n" " the first delimiter.\n" ). -spec matches_callback_pattern(binary(), binary()) -> boolean(). matches_callback_pattern(Key, Data) -> case gleam@string:split_once(Key, <<":"/utf8>>) of {ok, {<<"prefix"/utf8>>, Prefix}} -> gleam_stdlib:string_starts_with(Data, Prefix); {ok, {<<"contains"/utf8>>, Substr}} -> gleam_stdlib:contains_string(Data, Substr); {ok, {<<"suffix"/utf8>>, Suffix}} -> gleam_stdlib:string_ends_with(Data, Suffix); _ -> false end. -file("src/telega/router.gleam", 1958). ?DOC(" Find callback handler by pattern matching\n"). -spec find_callback_by_pattern( gleam@dict:dict(binary(), fun((telega@bot:context(ALBA, ALBB, ALBC), telega@update:update()) -> {ok, telega@bot:context(ALBA, ALBB, ALBC)} | {error, ALBB})), binary() ) -> gleam@option:option(fun((telega@bot:context(ALBA, ALBB, ALBC), telega@update:update()) -> {ok, telega@bot:context(ALBA, ALBB, ALBC)} | {error, ALBB})). find_callback_by_pattern(Callbacks, Data) -> _pipe = maps:to_list(Callbacks), _pipe@1 = gleam@list:find( _pipe, fun(Entry) -> {Key, _} = Entry, matches_callback_pattern(Key, Data) end ), _pipe@2 = gleam@result:map( _pipe@1, fun(Entry@1) -> {_, Handler} = Entry@1, Handler end ), gleam@option:from_result(_pipe@2). -file("src/telega/router.gleam", 1946). ?DOC(" Find callback handler by data string\n"). -spec find_callback_by_data( gleam@dict:dict(binary(), fun((telega@bot:context(ALAO, ALAP, ALAQ), telega@update:update()) -> {ok, telega@bot:context(ALAO, ALAP, ALAQ)} | {error, ALAP})), binary() ) -> gleam@option:option(fun((telega@bot:context(ALAO, ALAP, ALAQ), telega@update:update()) -> {ok, telega@bot:context(ALAO, ALAP, ALAQ)} | {error, ALAP})). find_callback_by_data(Callbacks, Data) -> case gleam_stdlib:map_get(Callbacks, Data) of {ok, Handler} -> {some, Handler}; {error, _} -> find_callback_by_pattern(Callbacks, Data) end. -file("src/telega/router.gleam", 1931). ?DOC(" Find a callback handler\n"). -spec find_callback_handler(router(ALAE, ALAF, ALAG), telega@update:update()) -> gleam@option:option(fun((telega@bot:context(ALAE, ALAF, ALAG), telega@update:update()) -> {ok, telega@bot:context(ALAE, ALAF, ALAG)} | {error, ALAF})). find_callback_handler(Router, Update) -> case {Router, Update} of {{router, _, _, Callbacks, _, _, _, _, _}, {callback_query_update, _, _, Query, _}} -> case erlang:element(7, Query) of {some, Data} -> find_callback_by_data(Callbacks, Data); none -> none end; {_, _} -> none end. -file("src/telega/router.gleam", 1906). ?DOC(" Find a command handler\n"). -spec find_command_handler( router(AKZR, AKZS, AKZT), telega@update:update(), telega@bot:context(AKZR, AKZS, AKZT) ) -> gleam@option:option(fun((telega@bot:context(AKZR, AKZS, AKZT), telega@update:update()) -> {ok, telega@bot:context(AKZR, AKZS, AKZT)} | {error, AKZS})). find_command_handler(Router, Update, Context) -> case {Router, Update} of {{router, Commands, _, _, _, _, _, _, _}, {command_update, _, _, Command, _, _}} -> Try_split = begin _pipe = erlang:element(3, Command), gleam@string:split_once(_pipe, <<"@"/utf8>>) end, Command_key = case {erlang:element(6, erlang:element(10, Context)), Try_split} of {{some, Bot_username}, {ok, {Cmd_text, Bot_suffix}}} when (Bot_username =:= Bot_suffix) andalso (Bot_username =/= <<""/utf8>>) -> Cmd_text; {_, _} -> erlang:element(3, Command) end, _pipe@1 = gleam_stdlib:map_get(Commands, Command_key), gleam@option:from_result(_pipe@1); {_, _} -> none end. -file("src/telega/router.gleam", 1887). ?DOC(" Find handler in a regular router (not composed)\n"). -spec find_handler_in_router( router(AKZF, AKZG, AKZH), telega@update:update(), telega@bot:context(AKZF, AKZG, AKZH) ) -> fun((telega@bot:context(AKZF, AKZG, AKZH), telega@update:update()) -> {ok, telega@bot:context(AKZF, AKZG, AKZH)} | {error, AKZG}). find_handler_in_router(Router, Update, Context) -> case Update of {command_update, _, _, _, _, _} -> _pipe = find_command_handler(Router, Update, Context), gleam@option:unwrap(_pipe, find_route_or_fallback(Router, Update)); {callback_query_update, _, _, _, _} -> _pipe@1 = find_callback_handler(Router, Update), gleam@option:unwrap(_pipe@1, find_route_or_fallback(Router, Update)); _ -> find_route_or_fallback(Router, Update) end. -file("src/telega/router.gleam", 1869). ?DOC(" Find handler in composed routers\n"). -spec find_handler_in_composed( list(router(AKYS, AKYT, AKYU)), telega@update:update(), telega@bot:context(AKYS, AKYT, AKYU) ) -> fun((telega@bot:context(AKYS, AKYT, AKYU), telega@update:update()) -> {ok, telega@bot:context(AKYS, AKYT, AKYU)} | {error, AKYT}). find_handler_in_composed(Routers, Update, Context) -> case Routers of [] -> fun(Ctx, _) -> {ok, Ctx} end; [Router | Rest] -> case can_handle_update(Router, Update) of true -> find_handler(Router, Update, Context); false -> find_handler_in_composed(Rest, Update, Context) end end. -file("src/telega/router.gleam", 1856). ?DOC(" Find the appropriate handler for an update\n"). -spec find_handler( router(AKYG, AKYH, AKYI), telega@update:update(), telega@bot:context(AKYG, AKYH, AKYI) ) -> fun((telega@bot:context(AKYG, AKYH, AKYI), telega@update:update()) -> {ok, telega@bot:context(AKYG, AKYH, AKYI)} | {error, AKYH}). find_handler(Router, Update, Context) -> case Router of {router, _, _, _, _, _, _, _, _} -> find_handler_in_router(Router, Update, Context); {composed_router, Composes, _} -> find_handler_in_composed(Composes, Update, Context) end. -file("src/telega/router.gleam", 1257). ?DOC(" Handle update through a list of composed routers\n"). -spec handle_composed( list(router(AKTT, AKTU, AKTV)), telega@bot:context(AKTT, AKTU, AKTV), telega@update:update() ) -> {ok, telega@bot:context(AKTT, AKTU, AKTV)} | {error, AKTU}. handle_composed(Routers, Ctx, Update) -> case Routers of [] -> {ok, Ctx}; [Router | Rest] -> case can_handle_update(Router, Update) of true -> handle(Router, Ctx, Update); false -> handle_composed(Rest, Ctx, Update) end end. -file("src/telega/router.gleam", 1231). ?DOC(" Process an update through the router\n"). -spec handle( router(AKTF, AKTG, AKTH), telega@bot:context(AKTF, AKTG, AKTH), telega@update:update() ) -> {ok, telega@bot:context(AKTF, AKTG, AKTH)} | {error, AKTG}. handle(Router, Ctx, Update) -> case Router of {router, _, _, _, _, _, Middleware, Catch_handler, _} -> Handler = find_handler(Router, Update, Ctx), Wrapped_handler = apply_middleware(Handler, Middleware), case Catch_handler of {some, Catch_fn} -> case Wrapped_handler(Ctx, Update) of {ok, Result} -> {ok, Result}; {error, Err} -> Catch_fn(Err) end; none -> Wrapped_handler(Ctx, Update) end; {composed_router, Composes, _} -> handle_composed(Composes, Ctx, Update) end. -file("src/telega/router.gleam", 1324). ?DOC(" Merge `incoming` into `base`, keeping `base`'s value on key conflicts.\n"). -spec merge_keeping_first( gleam@dict:dict(AKUU, AKUV), gleam@dict:dict(AKUU, AKUV) ) -> gleam@dict:dict(AKUU, AKUV). merge_keeping_first(Incoming, Base) -> gleam@dict:fold( Incoming, Base, fun(Acc, Key, Value) -> case gleam@dict:has_key(Acc, Key) of true -> Acc; false -> gleam@dict:insert(Acc, Key, Value) end end ). -file("src/telega/router.gleam", 1404). ?DOC(" Merge a list of routers into a single flat router\n"). -spec merge_routers(list(router(AKWN, AKWO, AKWP)), binary()) -> router(AKWN, AKWO, AKWP). merge_routers(Routers, Name) -> case Routers of [] -> {router, maps:new(), maps:new(), maps:new(), [], none, [], none, Name}; [Router] -> case Router of {router, _, _, _, _, _, _, _, _} = Router@1 -> {router, erlang:element(2, Router@1), erlang:element(3, Router@1), erlang:element(4, Router@1), erlang:element(5, Router@1), erlang:element(6, Router@1), erlang:element(7, Router@1), erlang:element(8, Router@1), Name}; {composed_router, _, _} -> Router end; [First | Rest] -> Merged_rest = merge_routers(Rest, Name), { First_commands@1, First_descriptions@1, First_callbacks@1, First_routes@1, First_fallback@1, First_middleware@1, First_catch_handler@1} = case First of {router, First_commands, First_descriptions, First_callbacks, First_routes, First_fallback, First_middleware, First_catch_handler, _} -> { First_commands, First_descriptions, First_callbacks, First_routes, First_fallback, First_middleware, First_catch_handler}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"telega/router"/utf8>>, function => <<"merge_routers"/utf8>>, line => 1430, value => _assert_fail, start => 45068, 'end' => 45380, pattern_start => 45079, pattern_end => 45372}) end, { Rest_commands@1, Rest_descriptions@1, Rest_callbacks@1, Rest_routes@1, Rest_fallback@1, Rest_middleware@1, Rest_catch_handler@1} = case Merged_rest of {router, Rest_commands, Rest_descriptions, Rest_callbacks, Rest_routes, Rest_fallback, Rest_middleware, Rest_catch_handler, _} -> { Rest_commands, Rest_descriptions, Rest_callbacks, Rest_routes, Rest_fallback, Rest_middleware, Rest_catch_handler}; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"telega/router"/utf8>>, function => <<"merge_routers"/utf8>>, line => 1441, value => _assert_fail@1, start => 45388, 'end' => 45699, pattern_start => 45399, pattern_end => 45685}) end, Merged_commands = merge_keeping_first( Rest_commands@1, First_commands@1 ), Merged_descriptions = merge_keeping_first( Rest_descriptions@1, First_descriptions@1 ), Merged_callbacks = merge_keeping_first( Rest_callbacks@1, First_callbacks@1 ), {router, Merged_commands, Merged_descriptions, Merged_callbacks, lists:append(First_routes@1, Rest_routes@1), gleam@option:'or'(First_fallback@1, Rest_fallback@1), lists:append(First_middleware@1, Rest_middleware@1), gleam@option:'or'(First_catch_handler@1, Rest_catch_handler@1), Name} end. -file("src/telega/router.gleam", 1390). ?DOC(" Convert any router (including ComposedRouter) to a flat Router with all routes merged\n"). -spec to_flat_router(router(AKWE, AKWF, AKWG)) -> router(AKWE, AKWF, AKWG). to_flat_router(Router) -> case Router of {router, _, _, _, _, _, _, _, _} -> Router; {composed_router, Composes, Name} -> Flattened = gleam@list:map(Composes, fun to_flat_router/1), merge_routers(Flattened, Name) end. -file("src/telega/router.gleam", 1276). ?DOC( " Merge two routers into one. All routes are combined, with first router's routes\n" " taking priority in case of conflicts. Middleware and catch handlers are shared.\n" ). -spec merge(router(AKUI, AKUJ, AKUK), router(AKUI, AKUJ, AKUK)) -> router(AKUI, AKUJ, AKUK). merge(First, Second) -> First_flat = to_flat_router(First), Second_flat = to_flat_router(Second), { First_commands@1, First_descriptions@1, First_callbacks@1, First_routes@1, First_fallback@1, First_middleware@1, First_catch_handler@1, First_name@1} = case First_flat of {router, First_commands, First_descriptions, First_callbacks, First_routes, First_fallback, First_middleware, First_catch_handler, First_name} -> { First_commands, First_descriptions, First_callbacks, First_routes, First_fallback, First_middleware, First_catch_handler, First_name}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"telega/router"/utf8>>, function => <<"merge"/utf8>>, line => 1284, value => _assert_fail, start => 40390, 'end' => 40685, pattern_start => 40401, pattern_end => 40672}) end, { Second_commands@1, Second_descriptions@1, Second_callbacks@1, Second_routes@1, Second_fallback@1, Second_middleware@1, Second_catch_handler@1, Second_name@1} = case Second_flat of {router, Second_commands, Second_descriptions, Second_callbacks, Second_routes, Second_fallback, Second_middleware, Second_catch_handler, Second_name} -> { Second_commands, Second_descriptions, Second_callbacks, Second_routes, Second_fallback, Second_middleware, Second_catch_handler, Second_name}; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"telega/router"/utf8>>, function => <<"merge"/utf8>>, line => 1295, value => _assert_fail@1, start => 40689, 'end' => 40993, pattern_start => 40700, pattern_end => 40979}) end, Merged_commands = merge_keeping_first(Second_commands@1, First_commands@1), Merged_descriptions = merge_keeping_first( Second_descriptions@1, First_descriptions@1 ), Merged_callbacks = merge_keeping_first( Second_callbacks@1, First_callbacks@1 ), {router, Merged_commands, Merged_descriptions, Merged_callbacks, lists:append(First_routes@1, Second_routes@1), gleam@option:'or'(First_fallback@1, Second_fallback@1), lists:append(First_middleware@1, Second_middleware@1), gleam@option:'or'(First_catch_handler@1, Second_catch_handler@1), <<<>/binary, Second_name@1/binary>>}. -file("src/telega/router.gleam", 1382). ?DOC(" Get the name of a router\n"). -spec get_router_name(router(any(), any(), any())) -> binary(). get_router_name(Router) -> case Router of {router, _, _, _, _, _, _, _, Name} -> Name; {composed_router, _, Name@1} -> Name@1 end. -file("src/telega/router.gleam", 1335). ?DOC( " Compose two routers, where each router maintains its own middleware and catch handlers.\n" " First router is tried first, if it doesn't handle the update, second router is tried.\n" ). -spec compose(router(AKVC, AKVD, AKVE), router(AKVC, AKVD, AKVE)) -> router(AKVC, AKVD, AKVE). compose(First, Second) -> case {First, Second} of {{composed_router, First_list, _}, {composed_router, Second_list, _}} -> {composed_router, lists:append(First_list, Second_list), <<<<(get_router_name(First))/binary, "+"/utf8>>/binary, (get_router_name(Second))/binary>>}; {{composed_router, Routers, _}, {router, _, _, _, _, _, _, _, _} = Second@1} -> {composed_router, lists:append(Routers, [Second@1]), <<<<(get_router_name(First))/binary, "+"/utf8>>/binary, (get_router_name(Second@1))/binary>>}; {{router, _, _, _, _, _, _, _, _} = First@1, {composed_router, Routers@1, _}} -> {composed_router, [First@1 | Routers@1], <<<<(get_router_name(First@1))/binary, "+"/utf8>>/binary, (get_router_name(Second))/binary>>}; {{router, _, _, _, _, _, _, _, _} = First@2, {router, _, _, _, _, _, _, _, _} = Second@2} -> {composed_router, [First@2, Second@2], <<<<(get_router_name(First@2))/binary, "+"/utf8>>/binary, (get_router_name(Second@2))/binary>>} end. -file("src/telega/router.gleam", 1367). ?DOC( " Compose multiple routers into one. Routers are tried in order.\n" " Each router maintains its own middleware and catch handlers.\n" ). -spec compose_many(list(router(AKVO, AKVP, AKVQ))) -> router(AKVO, AKVP, AKVQ). compose_many(Routers) -> case Routers of [] -> new(<<"empty"/utf8>>); [Router] -> Router; _ -> Names = gleam@list:map(Routers, fun get_router_name/1), Name = gleam@string:join(Names, <<"+"/utf8>>), {composed_router, Routers, Name} end. -file("src/telega/router.gleam", 1478). ?DOC( " List every command registered with a description, as `#(command, description)`\n" " pairs sorted by command name. Commands added with `on_command` (no\n" " description) are omitted. Flattens composed routers, so a fully composed\n" " router reports the union of its sub-routers' described commands.\n" "\n" " This is what `telega.with_auto_commands` feeds into `setMyCommands`.\n" ). -spec registered_commands(router(any(), any(), any())) -> list({binary(), binary()}). registered_commands(Router) -> Command_descriptions@1 = case to_flat_router(Router) of {router, _, Command_descriptions, _, _, _, _, _, _} -> Command_descriptions; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"telega/router"/utf8>>, function => <<"registered_commands"/utf8>>, line => 1481, value => _assert_fail, start => 46893, 'end' => 46962, pattern_start => 46904, pattern_end => 46937}) end, _pipe = Command_descriptions@1, _pipe@1 = maps:to_list(_pipe), gleam@list:sort( _pipe@1, fun(A, B) -> gleam@string:compare(erlang:element(1, A), erlang:element(1, B)) end ). -file("src/telega/router.gleam", 1534). ?DOC(" Map a concrete route to the Telegram `allowed_updates` string it consumes.\n"). -spec route_update_type(route(any(), any(), any())) -> binary(). route_update_type(Route) -> case Route of {text_pattern_route, _, _} -> <<"message"/utf8>>; {photo_route, _} -> <<"message"/utf8>>; {video_route, _} -> <<"message"/utf8>>; {voice_route, _} -> <<"message"/utf8>>; {audio_route, _} -> <<"message"/utf8>>; {media_group_route, _} -> <<"message"/utf8>>; {inline_query_route, _} -> <<"inline_query"/utf8>>; {chosen_inline_result_route, _} -> <<"chosen_inline_result"/utf8>>; {shipping_query_route, _} -> <<"shipping_query"/utf8>>; {pre_checkout_query_route, _} -> <<"pre_checkout_query"/utf8>>; {poll_route, _} -> <<"poll"/utf8>>; {poll_answer_route, _} -> <<"poll_answer"/utf8>>; {message_reaction_route, _} -> <<"message_reaction"/utf8>>; {message_reaction_emoji_route, _, _} -> <<"message_reaction"/utf8>>; {message_reaction_paid_route, _} -> <<"message_reaction"/utf8>>; {message_reaction_added_route, _} -> <<"message_reaction"/utf8>>; {message_reaction_removed_route, _} -> <<"message_reaction"/utf8>>; {message_reaction_count_route, _} -> <<"message_reaction_count"/utf8>>; {chat_member_updated_route, _} -> <<"chat_member"/utf8>>; {chat_join_request_route, _} -> <<"chat_join_request"/utf8>>; {custom_route, _, _} -> <<"message"/utf8>>; {filtered_route, _, _} -> <<"message"/utf8>> end. -file("src/telega/router.gleam", 1497). ?DOC( " Derive the set of Telegram update types this router actually handles, as the\n" " strings expected by `allowed_updates` (e.g. `\"message\"`, `\"callback_query\"`).\n" " The result is deduplicated and sorted for stable output.\n" "\n" " If the router has a fallback, custom, or filtered route, the handled set\n" " cannot be determined statically (those routes can match anything), so an\n" " empty list is returned to signal \"do not restrict\" — Telegram then sends its\n" " default update set. Use a manual override when you need narrowing alongside\n" " catch-all routes.\n" ). -spec allowed_updates(router(any(), any(), any())) -> list(binary()). allowed_updates(Router) -> {Commands@1, Callbacks@1, Routes@1, Fallback@1} = case to_flat_router( Router ) of {router, Commands, _, Callbacks, Routes, Fallback, _, _, _} -> { Commands, Callbacks, Routes, Fallback}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"telega/router"/utf8>>, function => <<"allowed_updates"/utf8>>, line => 1500, value => _assert_fail, start => 47723, 'end' => 47816, pattern_start => 47734, pattern_end => 47787}) end, Has_wildcard = gleam@option:is_some(Fallback@1) orelse gleam@list:any( Routes@1, fun(Route) -> case Route of {custom_route, _, _} -> true; {filtered_route, _, _} -> true; _ -> false end end ), case Has_wildcard of true -> []; false -> From_commands = case gleam@dict:is_empty(Commands@1) of true -> []; false -> [<<"message"/utf8>>] end, From_callbacks = case gleam@dict:is_empty(Callbacks@1) of true -> []; false -> [<<"callback_query"/utf8>>] end, From_routes = gleam@list:map(Routes@1, fun route_update_type/1), _pipe = [From_commands, From_callbacks, From_routes], _pipe@1 = lists:append(_pipe), _pipe@2 = gleam@list:unique(_pipe@1), gleam@list:sort(_pipe@2, fun gleam@string:compare/2) end. -file("src/telega/router.gleam", 1658). ?DOC(" Create a sub-router that processes updates within its own scope\n"). -spec scope( router(AKXX, AKXY, AKXZ), fun((telega@update:update()) -> boolean()) ) -> router(AKXX, AKXY, AKXZ). scope(Router, Predicate) -> case Router of {router, Commands, Command_descriptions, Callbacks, Routes, Fallback, Middleware, Catch_handler, Name} -> Scoped_handler = fun(Handler) -> fun(Ctx, Update) -> case Predicate(Update) of true -> Handler(Ctx, Update); false -> {ok, Ctx} end end end, Scope_route = fun(Route) -> case Route of {text_pattern_route, Pattern, Handler@1} -> {text_pattern_route, Pattern, fun(Ctx@1, Text) -> case Predicate(erlang:element(3, Ctx@1)) of true -> Handler@1(Ctx@1, Text); false -> {ok, Ctx@1} end end}; {photo_route, Handler@2} -> {photo_route, fun(Ctx@2, Photos) -> case Predicate(erlang:element(3, Ctx@2)) of true -> Handler@2(Ctx@2, Photos); false -> {ok, Ctx@2} end end}; {video_route, Handler@3} -> {video_route, fun(Ctx@3, Video) -> case Predicate(erlang:element(3, Ctx@3)) of true -> Handler@3(Ctx@3, Video); false -> {ok, Ctx@3} end end}; {voice_route, Handler@4} -> {voice_route, fun(Ctx@4, Voice) -> case Predicate(erlang:element(3, Ctx@4)) of true -> Handler@4(Ctx@4, Voice); false -> {ok, Ctx@4} end end}; {audio_route, Handler@5} -> {audio_route, fun(Ctx@5, Audio) -> case Predicate(erlang:element(3, Ctx@5)) of true -> Handler@5(Ctx@5, Audio); false -> {ok, Ctx@5} end end}; {media_group_route, Handler@6} -> {media_group_route, fun(Ctx@6, Media_group_id, Messages) -> case Predicate(erlang:element(3, Ctx@6)) of true -> Handler@6( Ctx@6, Media_group_id, Messages ); false -> {ok, Ctx@6} end end}; {inline_query_route, Handler@7} -> {inline_query_route, fun(Ctx@7, Inline_query) -> case Predicate(erlang:element(3, Ctx@7)) of true -> Handler@7(Ctx@7, Inline_query); false -> {ok, Ctx@7} end end}; {chosen_inline_result_route, Handler@8} -> {chosen_inline_result_route, fun(Ctx@8, Chosen_inline_result) -> case Predicate(erlang:element(3, Ctx@8)) of true -> Handler@8(Ctx@8, Chosen_inline_result); false -> {ok, Ctx@8} end end}; {shipping_query_route, Handler@9} -> {shipping_query_route, fun(Ctx@9, Shipping_query) -> case Predicate(erlang:element(3, Ctx@9)) of true -> Handler@9(Ctx@9, Shipping_query); false -> {ok, Ctx@9} end end}; {pre_checkout_query_route, Handler@10} -> {pre_checkout_query_route, fun(Ctx@10, Pre_checkout_query) -> case Predicate(erlang:element(3, Ctx@10)) of true -> Handler@10(Ctx@10, Pre_checkout_query); false -> {ok, Ctx@10} end end}; {poll_route, Handler@11} -> {poll_route, fun(Ctx@11, Poll) -> case Predicate(erlang:element(3, Ctx@11)) of true -> Handler@11(Ctx@11, Poll); false -> {ok, Ctx@11} end end}; {poll_answer_route, Handler@12} -> {poll_answer_route, fun(Ctx@12, Poll_answer) -> case Predicate(erlang:element(3, Ctx@12)) of true -> Handler@12(Ctx@12, Poll_answer); false -> {ok, Ctx@12} end end}; {message_reaction_route, Handler@13} -> {message_reaction_route, fun(Ctx@13, Message_reaction) -> case Predicate(erlang:element(3, Ctx@13)) of true -> Handler@13(Ctx@13, Message_reaction); false -> {ok, Ctx@13} end end}; {message_reaction_emoji_route, Emojis, Handler@14} -> {message_reaction_emoji_route, Emojis, fun(Ctx@14, Message_reaction@1) -> case Predicate(erlang:element(3, Ctx@14)) of true -> Handler@14(Ctx@14, Message_reaction@1); false -> {ok, Ctx@14} end end}; {message_reaction_paid_route, Handler@15} -> {message_reaction_paid_route, fun(Ctx@15, Message_reaction@2) -> case Predicate(erlang:element(3, Ctx@15)) of true -> Handler@15(Ctx@15, Message_reaction@2); false -> {ok, Ctx@15} end end}; {message_reaction_added_route, Handler@16} -> {message_reaction_added_route, fun(Ctx@16, Message_reaction@3) -> case Predicate(erlang:element(3, Ctx@16)) of true -> Handler@16(Ctx@16, Message_reaction@3); false -> {ok, Ctx@16} end end}; {message_reaction_removed_route, Handler@17} -> {message_reaction_removed_route, fun(Ctx@17, Message_reaction@4) -> case Predicate(erlang:element(3, Ctx@17)) of true -> Handler@17(Ctx@17, Message_reaction@4); false -> {ok, Ctx@17} end end}; {message_reaction_count_route, Handler@18} -> {message_reaction_count_route, fun(Ctx@18, Message_reaction_count) -> case Predicate(erlang:element(3, Ctx@18)) of true -> Handler@18( Ctx@18, Message_reaction_count ); false -> {ok, Ctx@18} end end}; {chat_member_updated_route, Handler@19} -> {chat_member_updated_route, fun(Ctx@19, Chat_member_updated) -> case Predicate(erlang:element(3, Ctx@19)) of true -> Handler@19(Ctx@19, Chat_member_updated); false -> {ok, Ctx@19} end end}; {chat_join_request_route, Handler@20} -> {chat_join_request_route, fun(Ctx@20, Chat_join_request) -> case Predicate(erlang:element(3, Ctx@20)) of true -> Handler@20(Ctx@20, Chat_join_request); false -> {ok, Ctx@20} end end}; {custom_route, Matcher, Handler@21} -> {custom_route, fun(Update@1) -> Predicate(Update@1) andalso Matcher(Update@1) end, Handler@21}; {filtered_route, F, Handler@22} -> {filtered_route, and2(filter(<<"scope"/utf8>>, Predicate), F), Handler@22} end end, {router, gleam@dict:map_values( Commands, fun(_, H) -> Scoped_handler(H) end ), Command_descriptions, gleam@dict:map_values( Callbacks, fun(_, H@1) -> Scoped_handler(H@1) end ), gleam@list:map(Routes, Scope_route), gleam@option:map(Fallback, Scoped_handler), Middleware, Catch_handler, <>}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 2225). ?DOC(" Logging middleware - logs update processing\n"). -spec with_logging( fun((telega@bot:context(ALDM, ALDN, ALDO), telega@update:update()) -> {ok, telega@bot:context(ALDM, ALDN, ALDO)} | {error, ALDN}) ) -> fun((telega@bot:context(ALDM, ALDN, ALDO), telega@update:update()) -> {ok, telega@bot:context(ALDM, ALDN, ALDO)} | {error, ALDN}). with_logging(Handler) -> fun(Ctx, Update_param) -> Update_type = telega@update:to_string(Update_param), telega@internal@log:info(<<"Processing "/utf8, Update_type/binary>>), case Handler(Ctx, Update_param) of {ok, Result} -> telega@internal@log:info( <<"Processed "/utf8, Update_type/binary>> ), {ok, Result}; {error, Err} -> telega@internal@log:error( <<<<<<"Failed to process "/utf8, Update_type/binary>>/binary, ": "/utf8>>/binary, (gleam@string:inspect(Err))/binary>> ), {error, Err} end end. -file("src/telega/router.gleam", 2248). ?DOC(" Filter middleware - only process updates that match predicate\n"). -spec with_filter( fun((telega@update:update()) -> boolean()), fun((telega@bot:context(ALDV, ALDW, ALDX), telega@update:update()) -> {ok, telega@bot:context(ALDV, ALDW, ALDX)} | {error, ALDW}) ) -> fun((telega@bot:context(ALDV, ALDW, ALDX), telega@update:update()) -> {ok, telega@bot:context(ALDV, ALDW, ALDX)} | {error, ALDW}). with_filter(Predicate, Handler) -> fun(Ctx, Update) -> case Predicate(Update) of true -> Handler(Ctx, Update); false -> {ok, Ctx} end end. -file("src/telega/router.gleam", 2261). ?DOC(" Error recovery middleware\n"). -spec with_recovery( fun((ALEE) -> {ok, telega@bot:context(ALEF, ALEE, ALEG)} | {error, ALEE}), fun((telega@bot:context(ALEF, ALEE, ALEG), telega@update:update()) -> {ok, telega@bot:context(ALEF, ALEE, ALEG)} | {error, ALEE}) ) -> fun((telega@bot:context(ALEF, ALEE, ALEG), telega@update:update()) -> {ok, telega@bot:context(ALEF, ALEE, ALEG)} | {error, ALEE}). with_recovery(Recover, Handler) -> fun(Ctx, Update) -> case Handler(Ctx, Update) of {ok, Result} -> {ok, Result}; {error, Err} -> Recover(Err) end end. -file("src/telega/router.gleam", 2296). ?DOC( " Per-user flood control middleware: allows at most `limit` updates per\n" " `window_ms` window for each `{chat_id}:{from_id}` pair. Counters live in\n" " ETS, so the limit is shared across all routes of the bot.\n" "\n" " `on_limit` is called instead of the handler when the limit is exceeded —\n" " pass `fn(ctx) { Ok(ctx) }` to drop the update silently, or reply from it\n" " to inform the user. Every rejected update emits a\n" " `telega.rate_limit.hit` telemetry event.\n" "\n" " Updates without user context (e.g. poll updates, `from_id` is `-1`) are\n" " not limited.\n" "\n" " ```gleam\n" " router.new(\"bot\")\n" " |> router.use_middleware(router.with_rate_limit(\n" " limit: 5,\n" " window_ms: 3000,\n" " on_limit: fn(ctx) { Ok(ctx) },\n" " ))\n" " ```\n" "\n" " Call `with_rate_limit` once at bot setup: the limiter's ETS table is owned\n" " by the calling process and is deleted when that process exits.\n" ). -spec with_rate_limit( integer(), integer(), fun((telega@bot:context(ALES, ALET, ALEU)) -> {ok, telega@bot:context(ALES, ALET, ALEU)} | {error, ALET}) ) -> fun((fun((telega@bot:context(ALES, ALET, ALEU), telega@update:update()) -> {ok, telega@bot:context(ALES, ALET, ALEU)} | {error, ALET})) -> fun((telega@bot:context(ALES, ALET, ALEU), telega@update:update()) -> {ok, telega@bot:context(ALES, ALET, ALEU)} | {error, ALET})). with_rate_limit(Limit, Window_ms, On_limit) -> Limiter = telega@internal@rate_limiter:new(Limit, Window_ms), fun(Handler) -> fun(Ctx, Update_param) -> gleam@bool:lazy_guard( erlang:element(2, Update_param) < 0, fun() -> Handler(Ctx, Update_param) end, fun() -> Key = <<<<(erlang:integer_to_binary( erlang:element(3, Update_param) ))/binary, ":"/utf8>>/binary, (erlang:integer_to_binary( erlang:element(2, Update_param) ))/binary>>, case telega@internal@rate_limiter:hit(Limiter, Key) of true -> Handler(Ctx, Update_param); false -> telega@telemetry:execute( [<<"telega"/utf8>>, <<"rate_limit"/utf8>>, <<"hit"/utf8>>], [{<<"count"/utf8>>, 1}], [{<<"chat_id"/utf8>>, {int_value, erlang:element(3, Update_param)}}, {<<"from_id"/utf8>>, {int_value, erlang:element(2, Update_param)}}, {<<"update_type"/utf8>>, {string_value, telega@update:to_string( Update_param )}}] ), On_limit(Ctx) end end ) end end.