-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_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, 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, fallback/2, use_middleware/2, with_catch_handler/2, handle/3, merge/2, compose/2, compose_many/1, scope/2, with_logging/1, with_filter/2, with_recovery/2]). -export_type([router/2, pattern/0, filter/0, route/2]). -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" " reply.with_text(ctx, \"Sorry, an error occurred\")\n" " })\n" " ```\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(AFIZ, AFJA) :: {router, gleam@dict:dict(binary(), fun((telega@bot:context(AFIZ, AFJA), telega@update:update()) -> {ok, telega@bot:context(AFIZ, AFJA)} | {error, AFJA})), gleam@dict:dict(binary(), fun((telega@bot:context(AFIZ, AFJA), telega@update:update()) -> {ok, telega@bot:context(AFIZ, AFJA)} | {error, AFJA})), list(route(AFIZ, AFJA)), gleam@option:option(fun((telega@bot:context(AFIZ, AFJA), telega@update:update()) -> {ok, telega@bot:context(AFIZ, AFJA)} | {error, AFJA})), list(fun((fun((telega@bot:context(AFIZ, AFJA), telega@update:update()) -> {ok, telega@bot:context(AFIZ, AFJA)} | {error, AFJA})) -> fun((telega@bot:context(AFIZ, AFJA), telega@update:update()) -> {ok, telega@bot:context(AFIZ, AFJA)} | {error, AFJA}))), gleam@option:option(fun((AFJA) -> {ok, telega@bot:context(AFIZ, AFJA)} | {error, AFJA})), binary()} | {composed_router, list(router(AFIZ, AFJA)), 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(AFJB, AFJC) :: {text_pattern_route, pattern(), fun((telega@bot:context(AFJB, AFJC), binary()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {photo_route, fun((telega@bot:context(AFJB, AFJC), list(telega@model@types:photo_size())) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {video_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:video()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {voice_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:voice()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {audio_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:audio()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {media_group_route, fun((telega@bot:context(AFJB, AFJC), binary(), list(telega@model@types:message())) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {inline_query_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:inline_query()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {chosen_inline_result_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:chosen_inline_result()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {shipping_query_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:shipping_query()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {pre_checkout_query_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:pre_checkout_query()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {poll_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:poll()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {poll_answer_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:poll_answer()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {message_reaction_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {message_reaction_emoji_route, list(binary()), fun((telega@bot:context(AFJB, AFJC), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {message_reaction_paid_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {message_reaction_added_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {message_reaction_removed_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {message_reaction_count_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:message_reaction_count_updated()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {chat_member_updated_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:chat_member_updated()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {chat_join_request_route, fun((telega@bot:context(AFJB, AFJC), telega@model@types:chat_join_request()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {custom_route, fun((telega@update:update()) -> boolean()), fun((telega@bot:context(AFJB, AFJC), telega@update:update()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})} | {filtered_route, filter(), fun((telega@bot:context(AFJB, AFJC), telega@update:update()) -> {ok, telega@bot:context(AFJB, AFJC)} | {error, AFJC})}. -file("src/telega/router.gleam", 433). ?DOC(" Create a new router\n"). -spec new(binary()) -> router(any(), any()). new(Name) -> {router, maps:new(), maps:new(), [], none, [], none, Name}. -file("src/telega/router.gleam", 446). ?DOC(" Add a command handler\n"). -spec on_command( router(AFPT, AFPU), binary(), fun((telega@bot:context(AFPT, AFPU), telega@update:command()) -> {ok, telega@bot:context(AFPT, AFPU)} | {error, AFPU}) ) -> router(AFPT, AFPU). on_command(Router, Command, Handler) -> case Router of {router, Commands, _, _, _, _, _, _} -> Command_key = case gleam_stdlib:string_starts_with( Command, <<"/"/utf8>> ) of true -> gleam@string:drop_start(Command, 1); false -> Command end, 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)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 474). ?DOC(" Add multiple commands with same handler\n"). -spec on_commands( router(AFQB, AFQC), list(binary()), fun((telega@bot:context(AFQB, AFQC), telega@update:command()) -> {ok, telega@bot:context(AFQB, AFQC)} | {error, AFQC}) ) -> router(AFQB, AFQC). on_commands(Router, Commands, Handler) -> gleam@list:fold( Commands, Router, fun(R, Cmd) -> on_command(R, Cmd, Handler) end ). -file("src/telega/router.gleam", 483). ?DOC(" Add a text handler with pattern\n"). -spec on_text( router(AFQK, AFQL), pattern(), fun((telega@bot:context(AFQK, AFQL), binary()) -> {ok, telega@bot:context(AFQK, AFQL)} | {error, AFQL}) ) -> router(AFQK, AFQL). on_text(Router, Pattern, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{text_pattern_route, Pattern, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 496). ?DOC(" Add a handler for any text\n"). -spec on_any_text( router(AFQS, AFQT), fun((telega@bot:context(AFQS, AFQT), binary()) -> {ok, telega@bot:context(AFQS, AFQT)} | {error, AFQT}) ) -> router(AFQS, AFQT). on_any_text(Router, Handler) -> on_text(Router, {prefix, <<""/utf8>>}, Handler). -file("src/telega/router.gleam", 504). ?DOC(" Add a callback query handler with pattern\n"). -spec on_callback( router(AFRA, AFRB), pattern(), fun((telega@bot:context(AFRA, AFRB), binary(), binary()) -> {ok, telega@bot:context(AFRA, AFRB)} | {error, AFRB}) ) -> router(AFRA, AFRB). 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), gleam@dict:insert(Callbacks, Key, Wrapped_handler), erlang:element(4, Router), erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 535). ?DOC(" Add handlers for media types\n"). -spec on_photo( router(AFRI, AFRJ), fun((telega@bot:context(AFRI, AFRJ), list(telega@model@types:photo_size())) -> {ok, telega@bot:context(AFRI, AFRJ)} | {error, AFRJ}) ) -> router(AFRI, AFRJ). on_photo(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{photo_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 546). -spec on_video( router(AFRQ, AFRR), fun((telega@bot:context(AFRQ, AFRR), telega@model@types:video()) -> {ok, telega@bot:context(AFRQ, AFRR)} | {error, AFRR}) ) -> router(AFRQ, AFRR). on_video(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{video_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 557). -spec on_voice( router(AFRY, AFRZ), fun((telega@bot:context(AFRY, AFRZ), telega@model@types:voice()) -> {ok, telega@bot:context(AFRY, AFRZ)} | {error, AFRZ}) ) -> router(AFRY, AFRZ). on_voice(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{voice_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 568). -spec on_audio( router(AFSG, AFSH), fun((telega@bot:context(AFSG, AFSH), telega@model@types:audio()) -> {ok, telega@bot:context(AFSG, AFSH)} | {error, AFSH}) ) -> router(AFSG, AFSH). on_audio(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{audio_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 580). ?DOC(" Add handler for media groups (albums of photos/videos)\n"). -spec on_media_group( router(AFSO, AFSP), fun((telega@bot:context(AFSO, AFSP), binary(), list(telega@model@types:message())) -> {ok, telega@bot:context(AFSO, AFSP)} | {error, AFSP}) ) -> router(AFSO, AFSP). on_media_group(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{media_group_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 592). ?DOC(" Add handler for inline queries\n"). -spec on_inline_query( router(AFSW, AFSX), fun((telega@bot:context(AFSW, AFSX), telega@model@types:inline_query()) -> {ok, telega@bot:context(AFSW, AFSX)} | {error, AFSX}) ) -> router(AFSW, AFSX). on_inline_query(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{inline_query_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 604). ?DOC(" Add handler for chosen inline results\n"). -spec on_chosen_inline_result( router(AFTE, AFTF), fun((telega@bot:context(AFTE, AFTF), telega@model@types:chosen_inline_result()) -> {ok, telega@bot:context(AFTE, AFTF)} | {error, AFTF}) ) -> router(AFTE, AFTF). on_chosen_inline_result(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{chosen_inline_result_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 616). ?DOC(" Add handler for shipping queries (payments)\n"). -spec on_shipping_query( router(AFTM, AFTN), fun((telega@bot:context(AFTM, AFTN), telega@model@types:shipping_query()) -> {ok, telega@bot:context(AFTM, AFTN)} | {error, AFTN}) ) -> router(AFTM, AFTN). on_shipping_query(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{shipping_query_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 628). ?DOC(" Add handler for pre-checkout queries (payments)\n"). -spec on_pre_checkout_query( router(AFTU, AFTV), fun((telega@bot:context(AFTU, AFTV), telega@model@types:pre_checkout_query()) -> {ok, telega@bot:context(AFTU, AFTV)} | {error, AFTV}) ) -> router(AFTU, AFTV). on_pre_checkout_query(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{pre_checkout_query_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 640). ?DOC(" Add handler for poll updates\n"). -spec on_poll( router(AFUC, AFUD), fun((telega@bot:context(AFUC, AFUD), telega@model@types:poll()) -> {ok, telega@bot:context(AFUC, AFUD)} | {error, AFUD}) ) -> router(AFUC, AFUD). on_poll(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{poll_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 652). ?DOC(" Add handler for poll answer updates\n"). -spec on_poll_answer( router(AFUK, AFUL), fun((telega@bot:context(AFUK, AFUL), telega@model@types:poll_answer()) -> {ok, telega@bot:context(AFUK, AFUL)} | {error, AFUL}) ) -> router(AFUK, AFUL). on_poll_answer(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{poll_answer_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 664). ?DOC(" Add handler for message reactions\n"). -spec on_reaction( router(AFUS, AFUT), fun((telega@bot:context(AFUS, AFUT), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFUS, AFUT)} | {error, AFUT}) ) -> router(AFUS, AFUT). on_reaction(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{message_reaction_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 682). ?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(AFVA, AFVB), binary(), fun((telega@bot:context(AFVA, AFVB), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFVA, AFVB)} | {error, AFVB}) ) -> router(AFVA, AFVB). on_reaction_emoji(Router, Emoji, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{message_reaction_emoji_route, [Emoji], Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 704). ?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(AFVI, AFVJ), list(binary()), fun((telega@bot:context(AFVI, AFVJ), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFVI, AFVJ)} | {error, AFVJ}) ) -> router(AFVI, AFVJ). on_reaction_emojis(Router, Emojis, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{message_reaction_emoji_route, Emojis, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 726). ?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(AFVR, AFVS), fun((telega@bot:context(AFVR, AFVS), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFVR, AFVS)} | {error, AFVS}) ) -> router(AFVR, AFVS). on_paid_reaction(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{message_reaction_paid_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 744). ?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(AFVZ, AFWA), fun((telega@bot:context(AFVZ, AFWA), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFVZ, AFWA)} | {error, AFWA}) ) -> router(AFVZ, AFWA). on_reaction_added(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{message_reaction_added_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 762). ?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(AFWH, AFWI), fun((telega@bot:context(AFWH, AFWI), telega@model@types:message_reaction_updated()) -> {ok, telega@bot:context(AFWH, AFWI)} | {error, AFWI}) ) -> router(AFWH, AFWI). on_reaction_removed(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{message_reaction_removed_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 780). ?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(AFWP, AFWQ), fun((telega@bot:context(AFWP, AFWQ), telega@model@types:message_reaction_count_updated()) -> {ok, telega@bot:context(AFWP, AFWQ)} | {error, AFWQ}) ) -> router(AFWP, AFWQ). on_reaction_count(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{message_reaction_count_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 792). ?DOC(" Add handler for chat member updates\n"). -spec on_chat_member_updated( router(AFWX, AFWY), fun((telega@bot:context(AFWX, AFWY), telega@model@types:chat_member_updated()) -> {ok, telega@bot:context(AFWX, AFWY)} | {error, AFWY}) ) -> router(AFWX, AFWY). on_chat_member_updated(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{chat_member_updated_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 804). ?DOC(" Add handler for chat join requests\n"). -spec on_chat_join_request( router(AFXF, AFXG), fun((telega@bot:context(AFXF, AFXG), telega@model@types:chat_join_request()) -> {ok, telega@bot:context(AFXF, AFXG)} | {error, AFXG}) ) -> router(AFXF, AFXG). on_chat_join_request(Router, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{chat_join_request_route, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 816). ?DOC(" Add a custom route with matcher function\n"). -spec on_custom( router(AFXN, AFXO), fun((telega@update:update()) -> boolean()), fun((telega@bot:context(AFXN, AFXO), telega@update:update()) -> {ok, telega@bot:context(AFXN, AFXO)} | {error, AFXO}) ) -> router(AFXN, AFXO). on_custom(Router, Matcher, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{custom_route, Matcher, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 829). ?DOC(" Add a filtered route\n"). -spec on_filtered( router(AFXV, AFXW), filter(), fun((telega@bot:context(AFXV, AFXW), telega@update:update()) -> {ok, telega@bot:context(AFXV, AFXW)} | {error, AFXW}) ) -> router(AFXV, AFXW). on_filtered(Router, Filter, Handler) -> case Router of {router, _, _, Routes, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), [{filtered_route, Filter, Handler} | Routes], erlang:element(5, Router), erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 842). ?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", 847). ?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", 857). ?DOC(" Combine two filters with AND logic\n"). -spec and2(filter(), filter()) -> filter(). and2(Left, Right) -> {'and', Left, Right}. -file("src/telega/router.gleam", 862). ?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", 872). ?DOC(" Combine two filters with OR logic\n"). -spec or2(filter(), filter()) -> filter(). or2(Left, Right) -> {'or', Left, Right}. -file("src/telega/router.gleam", 877). ?DOC(" Negate a filter\n"). -spec 'not'(filter()) -> filter(). 'not'(F) -> {'not', F}. -file("src/telega/router.gleam", 882). ?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", 892). ?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", 902). ?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", 912). ?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", 922). ?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", 932). ?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", 942). ?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", 958). ?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", 975). ?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) -> case Update of {text_update, _, Cid, _, _, _} -> Cid =:= Chat_id; {command_update, _, Cid@1, _, _, _} -> Cid@1 =:= Chat_id; {callback_query_update, _, Cid@2, _, _} -> Cid@2 =:= Chat_id; {photo_update, _, Cid@3, _, _, _} -> Cid@3 =:= Chat_id; {video_update, _, Cid@4, _, _, _} -> Cid@4 =:= Chat_id; {voice_update, _, Cid@5, _, _, _} -> Cid@5 =:= Chat_id; {audio_update, _, Cid@6, _, _, _} -> Cid@6 =:= Chat_id; _ -> false end end ). -file("src/telega/router.gleam", 992). ?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", 998). ?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", 1003). ?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", 1013). ?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", 1023). ?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", 1033). ?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", 1046). ?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", 1056). ?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", 1070). ?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", 1082). ?DOC(" Set fallback handler for unmatched updates\n"). -spec fallback( router(AFYG, AFYH), fun((telega@bot:context(AFYG, AFYH), telega@update:update()) -> {ok, telega@bot:context(AFYG, AFYH)} | {error, AFYH}) ) -> router(AFYG, AFYH). fallback(Router, Handler) -> case Router of {router, _, _, _, _, _, _, _} -> {router, erlang:element(2, Router), erlang:element(3, Router), erlang:element(4, Router), {some, Handler}, erlang:element(6, Router), erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 1093). ?DOC(" Add middleware to the router\n"). -spec use_middleware( router(AFYO, AFYP), fun((fun((telega@bot:context(AFYO, AFYP), telega@update:update()) -> {ok, telega@bot:context(AFYO, AFYP)} | {error, AFYP})) -> fun((telega@bot:context(AFYO, AFYP), telega@update:update()) -> {ok, telega@bot:context(AFYO, AFYP)} | {error, AFYP})) ) -> router(AFYO, AFYP). 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), [Middleware | Existing_middleware], erlang:element(7, Router), erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 1105). ?DOC(" Add a catch handler to the router that handles errors from all routes\n"). -spec with_catch_handler( router(AFYW, AFYX), fun((AFYX) -> {ok, telega@bot:context(AFYW, AFYX)} | {error, AFYX}) ) -> router(AFYW, AFYX). 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), {some, Catch_handler}, erlang:element(8, Router)}; {composed_router, _, _} = Composed -> Composed end. -file("src/telega/router.gleam", 1957). ?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", 1377). ?DOC(" Check if a router can handle a given update (has specific routes or fallback)\n"). -spec can_handle_update(router(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", 2022). ?DOC(" Apply middleware to a handler\n"). -spec apply_middleware( fun((telega@bot:context(AGFI, AGFJ), telega@update:update()) -> {ok, telega@bot:context(AGFI, AGFJ)} | {error, AGFJ}), list(fun((fun((telega@bot:context(AGFI, AGFJ), telega@update:update()) -> {ok, telega@bot:context(AGFI, AGFJ)} | {error, AGFJ})) -> fun((telega@bot:context(AGFI, AGFJ), telega@update:update()) -> {ok, telega@bot:context(AGFI, AGFJ)} | {error, AGFJ}))) ) -> fun((telega@bot:context(AGFI, AGFJ), telega@update:update()) -> {ok, telega@bot:context(AGFI, AGFJ)} | {error, AGFJ}). apply_middleware(Handler, Middleware) -> gleam@list:fold(Middleware, Handler, fun(H, Mw) -> Mw(H) end). -file("src/telega/router.gleam", 2008). ?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", 1999). ?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", 1990). ?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", 1980). ?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", 1967). ?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", 1919). ?DOC(" Check if a route matches an update\n"). -spec route_matches(route(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", 1832). ?DOC(" Find matching route for an update\n"). -spec find_matching_route(list(route(AGET, AGEU)), telega@update:update()) -> gleam@option:option(fun((telega@bot:context(AGET, AGEU), telega@update:update()) -> {ok, telega@bot:context(AGET, AGEU)} | {error, AGEU})). 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", 1816). ?DOC(" Find route or fallback in composed routers\n"). -spec find_route_or_fallback_in_composed( list(router(AGEM, AGEN)), telega@update:update() ) -> fun((telega@bot:context(AGEM, AGEN), telega@update:update()) -> {ok, telega@bot:context(AGEM, AGEN)} | {error, AGEN}). 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", 1795). ?DOC(" Try routes, then fallback\n"). -spec find_route_or_fallback(router(AGEG, AGEH), telega@update:update()) -> fun((telega@bot:context(AGEG, AGEH), telega@update:update()) -> {ok, telega@bot:context(AGEG, AGEH)} | {error, AGEH}). 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", 1785). ?DOC(" Check if a callback pattern key matches the data\n"). -spec matches_callback_pattern(binary(), binary()) -> boolean(). matches_callback_pattern(Key, Data) -> 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); _ -> false end. -file("src/telega/router.gleam", 1768). ?DOC(" Find callback handler by pattern matching\n"). -spec find_callback_by_pattern( gleam@dict:dict(binary(), fun((telega@bot:context(AGDX, AGDY), telega@update:update()) -> {ok, telega@bot:context(AGDX, AGDY)} | {error, AGDY})), binary() ) -> gleam@option:option(fun((telega@bot:context(AGDX, AGDY), telega@update:update()) -> {ok, telega@bot:context(AGDX, AGDY)} | {error, AGDY})). 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", 1756). ?DOC(" Find callback handler by data string\n"). -spec find_callback_by_data( gleam@dict:dict(binary(), fun((telega@bot:context(AGDO, AGDP), telega@update:update()) -> {ok, telega@bot:context(AGDO, AGDP)} | {error, AGDP})), binary() ) -> gleam@option:option(fun((telega@bot:context(AGDO, AGDP), telega@update:update()) -> {ok, telega@bot:context(AGDO, AGDP)} | {error, AGDP})). 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", 1741). ?DOC(" Find a callback handler\n"). -spec find_callback_handler(router(AGDH, AGDI), telega@update:update()) -> gleam@option:option(fun((telega@bot:context(AGDH, AGDI), telega@update:update()) -> {ok, telega@bot:context(AGDH, AGDI)} | {error, AGDI})). 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", 1716). ?DOC(" Find a command handler\n"). -spec find_command_handler( router(AGCY, AGCZ), telega@update:update(), telega@bot:context(AGCY, AGCZ) ) -> gleam@option:option(fun((telega@bot:context(AGCY, AGCZ), telega@update:update()) -> {ok, telega@bot:context(AGCY, AGCZ)} | {error, AGCZ})). 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(9, 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", 1697). ?DOC(" Find handler in a regular router (not composed)\n"). -spec find_handler_in_router( router(AGCQ, AGCR), telega@update:update(), telega@bot:context(AGCQ, AGCR) ) -> fun((telega@bot:context(AGCQ, AGCR), telega@update:update()) -> {ok, telega@bot:context(AGCQ, AGCR)} | {error, AGCR}). 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", 1679). ?DOC(" Find handler in composed routers\n"). -spec find_handler_in_composed( list(router(AGCH, AGCI)), telega@update:update(), telega@bot:context(AGCH, AGCI) ) -> fun((telega@bot:context(AGCH, AGCI), telega@update:update()) -> {ok, telega@bot:context(AGCH, AGCI)} | {error, AGCI}). 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", 1666). ?DOC(" Find the appropriate handler for an update\n"). -spec find_handler( router(AGBZ, AGCA), telega@update:update(), telega@bot:context(AGBZ, AGCA) ) -> fun((telega@bot:context(AGBZ, AGCA), telega@update:update()) -> {ok, telega@bot:context(AGBZ, AGCA)} | {error, AGCA}). 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", 1142). ?DOC(" Handle update through a list of composed routers\n"). -spec handle_composed( list(router(AFZQ, AFZR)), telega@bot:context(AFZQ, AFZR), telega@update:update() ) -> {ok, telega@bot:context(AFZQ, AFZR)} | {error, AFZR}. 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", 1116). ?DOC(" Process an update through the router\n"). -spec handle( router(AFZG, AFZH), telega@bot:context(AFZG, AFZH), telega@update:update() ) -> {ok, telega@bot:context(AFZG, AFZH)} | {error, AFZH}. 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", 1285). ?DOC(" Merge a list of routers into a single flat router\n"). -spec merge_routers(list(router(AGBI, AGBJ)), binary()) -> router(AGBI, AGBJ). merge_routers(Routers, Name) -> case Routers of [] -> {router, maps:new(), maps:new(), [], none, [], none, Name}; [Router] -> case Router of {router, Commands, Callbacks, Routes, Fallback, Middleware, Catch_handler, _} -> {router, Commands, Callbacks, Routes, Fallback, Middleware, Catch_handler, Name}; {composed_router, _, _} -> Router end; [First | Rest] -> Merged_rest = merge_routers(Rest, Name), { First_commands@1, First_callbacks@1, First_routes@1, First_fallback@1, First_middleware@1, First_catch_handler@1} = case First of {router, First_commands, First_callbacks, First_routes, First_fallback, First_middleware, First_catch_handler, _} -> { First_commands, 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 => 1327, value => _assert_fail, start => 39430, 'end' => 39692, pattern_start => 39441, pattern_end => 39684}) end, { Rest_commands@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_callbacks, Rest_routes, Rest_fallback, Rest_middleware, Rest_catch_handler, _} -> { Rest_commands, 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 => 1337, value => _assert_fail@1, start => 39700, 'end' => 39962, pattern_start => 39711, pattern_end => 39948}) end, Merged_commands = gleam@dict:fold( Rest_commands@1, First_commands@1, fun(Acc, Key, Value) -> case gleam@dict:has_key(Acc, Key) of true -> Acc; false -> gleam@dict:insert(Acc, Key, Value) end end ), Merged_callbacks = gleam@dict:fold( Rest_callbacks@1, First_callbacks@1, fun(Acc@1, Key@1, Value@1) -> case gleam@dict:has_key(Acc@1, Key@1) of true -> Acc@1; false -> gleam@dict:insert(Acc@1, Key@1, Value@1) end end ), {router, Merged_commands, 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", 1273). ?DOC(" Convert any router (including ComposedRouter) to a flat Router with all routes merged\n"). -spec to_flat_router(router(AGBC, AGBD)) -> router(AGBC, AGBD). 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", 1161). ?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(AGAB, AGAC), router(AGAB, AGAC)) -> router(AGAB, AGAC). merge(First, Second) -> First_flat = to_flat_router(First), Second_flat = to_flat_router(Second), { First_commands@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_callbacks, First_routes, First_fallback, First_middleware, First_catch_handler, First_name} -> { First_commands, 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 => 1169, value => _assert_fail, start => 34847, 'end' => 35096, pattern_start => 34858, pattern_end => 35083}) end, { Second_commands@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_callbacks, Second_routes, Second_fallback, Second_middleware, Second_catch_handler, Second_name} -> { Second_commands, 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 => 1179, value => _assert_fail@1, start => 35100, 'end' => 35357, pattern_start => 35111, pattern_end => 35343}) end, Merged_commands = gleam@dict:fold( Second_commands@1, First_commands@1, fun(Acc, Key, Value) -> case gleam@dict:has_key(Acc, Key) of true -> Acc; false -> gleam@dict:insert(Acc, Key, Value) end end ), Merged_callbacks = gleam@dict:fold( Second_callbacks@1, First_callbacks@1, fun(Acc@1, Key@1, Value@1) -> case gleam@dict:has_key(Acc@1, Key@1) of true -> Acc@1; false -> gleam@dict:insert(Acc@1, Key@1, Value@1) end end ), {router, Merged_commands, 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", 1265). ?DOC(" Get the name of a router\n"). -spec get_router_name(router(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", 1218). ?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(AGAJ, AGAK), router(AGAJ, AGAK)) -> router(AGAJ, AGAK). 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", 1250). ?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(AGAR, AGAS))) -> router(AGAR, AGAS). 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", 1470). ?DOC(" Create a sub-router that processes updates within its own scope\n"). -spec scope(router(AGBT, AGBU), fun((telega@update:update()) -> boolean())) -> router(AGBT, AGBU). scope(Router, Predicate) -> case Router of {router, Commands, 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 ), 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", 2030). ?DOC(" Logging middleware - logs update processing\n"). -spec with_logging( fun((telega@bot:context(AGFR, AGFS), telega@update:update()) -> {ok, telega@bot:context(AGFR, AGFS)} | {error, AGFS}) ) -> fun((telega@bot:context(AGFR, AGFS), telega@update:update()) -> {ok, telega@bot:context(AGFR, AGFS)} | {error, AGFS}). 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", 2053). ?DOC(" Filter middleware - only process updates that match predicate\n"). -spec with_filter( fun((telega@update:update()) -> boolean()), fun((telega@bot:context(AGFX, AGFY), telega@update:update()) -> {ok, telega@bot:context(AGFX, AGFY)} | {error, AGFY}) ) -> fun((telega@bot:context(AGFX, AGFY), telega@update:update()) -> {ok, telega@bot:context(AGFX, AGFY)} | {error, AGFY}). 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", 2066). ?DOC(" Error recovery middleware\n"). -spec with_recovery( fun((AGGD) -> {ok, telega@bot:context(AGGE, AGGD)} | {error, AGGD}), fun((telega@bot:context(AGGE, AGGD), telega@update:update()) -> {ok, telega@bot:context(AGGE, AGGD)} | {error, AGGD}) ) -> fun((telega@bot:context(AGGE, AGGD), telega@update:update()) -> {ok, telega@bot:context(AGGE, AGGD)} | {error, AGGD}). with_recovery(Recover, Handler) -> fun(Ctx, Update) -> case Handler(Ctx, Update) of {ok, Result} -> {ok, Result}; {error, Err} -> Recover(Err) end end.