Current section

Files

Jump to
telega src telega@router.erl
Raw

src/telega@router.erl

-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_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, compose/2, compose_many/1, merge/2, scope/2, with_logging/1, with_filter/2, with_recovery/2, handle/3]).
-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(AMDH, AMDI) :: {router,
gleam@dict:dict(binary(), fun((telega@bot:context(AMDH, AMDI), telega@update:update()) -> {ok,
telega@bot:context(AMDH, AMDI)} |
{error, AMDI})),
gleam@dict:dict(binary(), fun((telega@bot:context(AMDH, AMDI), telega@update:update()) -> {ok,
telega@bot:context(AMDH, AMDI)} |
{error, AMDI})),
list(route(AMDH, AMDI)),
gleam@option:option(fun((telega@bot:context(AMDH, AMDI), telega@update:update()) -> {ok,
telega@bot:context(AMDH, AMDI)} |
{error, AMDI})),
list(fun((fun((telega@bot:context(AMDH, AMDI), telega@update:update()) -> {ok,
telega@bot:context(AMDH, AMDI)} |
{error, AMDI})) -> fun((telega@bot:context(AMDH, AMDI), telega@update:update()) -> {ok,
telega@bot:context(AMDH, AMDI)} |
{error, AMDI}))),
gleam@option:option(fun((AMDI) -> {ok, telega@bot:context(AMDH, AMDI)} |
{error, AMDI})),
binary()} |
{composed_router, list(router(AMDH, AMDI)), 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(AMDJ, AMDK) :: {text_pattern_route,
pattern(),
fun((telega@bot:context(AMDJ, AMDK), binary()) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})} |
{photo_route,
fun((telega@bot:context(AMDJ, AMDK), list(telega@model@types:photo_size())) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})} |
{video_route,
fun((telega@bot:context(AMDJ, AMDK), telega@model@types:video()) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})} |
{voice_route,
fun((telega@bot:context(AMDJ, AMDK), telega@model@types:voice()) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})} |
{audio_route,
fun((telega@bot:context(AMDJ, AMDK), telega@model@types:audio()) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})} |
{media_group_route,
fun((telega@bot:context(AMDJ, AMDK), binary(), list(telega@model@types:message())) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})} |
{custom_route,
fun((telega@update:update()) -> boolean()),
fun((telega@bot:context(AMDJ, AMDK), telega@update:update()) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})} |
{filtered_route,
filter(),
fun((telega@bot:context(AMDJ, AMDK), telega@update:update()) -> {ok,
telega@bot:context(AMDJ, AMDK)} |
{error, AMDK})}.
-file("src/telega/router.gleam", 370).
?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", 383).
?DOC(" Add a command handler\n").
-spec on_command(
router(AMGZ, AMHA),
binary(),
fun((telega@bot:context(AMGZ, AMHA), telega@update:command()) -> {ok,
telega@bot:context(AMGZ, AMHA)} |
{error, AMHA})
) -> router(AMGZ, AMHA).
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", 411).
?DOC(" Add multiple commands with same handler\n").
-spec on_commands(
router(AMHH, AMHI),
list(binary()),
fun((telega@bot:context(AMHH, AMHI), telega@update:command()) -> {ok,
telega@bot:context(AMHH, AMHI)} |
{error, AMHI})
) -> router(AMHH, AMHI).
on_commands(Router, Commands, Handler) ->
gleam@list:fold(
Commands,
Router,
fun(R, Cmd) -> on_command(R, Cmd, Handler) end
).
-file("src/telega/router.gleam", 420).
?DOC(" Add a text handler with pattern\n").
-spec on_text(
router(AMHQ, AMHR),
pattern(),
fun((telega@bot:context(AMHQ, AMHR), binary()) -> {ok,
telega@bot:context(AMHQ, AMHR)} |
{error, AMHR})
) -> router(AMHQ, AMHR).
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", 433).
?DOC(" Add a handler for any text\n").
-spec on_any_text(
router(AMHY, AMHZ),
fun((telega@bot:context(AMHY, AMHZ), binary()) -> {ok,
telega@bot:context(AMHY, AMHZ)} |
{error, AMHZ})
) -> router(AMHY, AMHZ).
on_any_text(Router, Handler) ->
on_text(Router, {prefix, <<""/utf8>>}, Handler).
-file("src/telega/router.gleam", 441).
?DOC(" Add a callback query handler with pattern\n").
-spec on_callback(
router(AMIG, AMIH),
pattern(),
fun((telega@bot:context(AMIG, AMIH), binary(), binary()) -> {ok,
telega@bot:context(AMIG, AMIH)} |
{error, AMIH})
) -> router(AMIG, AMIH).
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", 472).
?DOC(" Add handlers for media types\n").
-spec on_photo(
router(AMIO, AMIP),
fun((telega@bot:context(AMIO, AMIP), list(telega@model@types:photo_size())) -> {ok,
telega@bot:context(AMIO, AMIP)} |
{error, AMIP})
) -> router(AMIO, AMIP).
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", 483).
-spec on_video(
router(AMIW, AMIX),
fun((telega@bot:context(AMIW, AMIX), telega@model@types:video()) -> {ok,
telega@bot:context(AMIW, AMIX)} |
{error, AMIX})
) -> router(AMIW, AMIX).
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", 494).
-spec on_voice(
router(AMJE, AMJF),
fun((telega@bot:context(AMJE, AMJF), telega@model@types:voice()) -> {ok,
telega@bot:context(AMJE, AMJF)} |
{error, AMJF})
) -> router(AMJE, AMJF).
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", 505).
-spec on_audio(
router(AMJM, AMJN),
fun((telega@bot:context(AMJM, AMJN), telega@model@types:audio()) -> {ok,
telega@bot:context(AMJM, AMJN)} |
{error, AMJN})
) -> router(AMJM, AMJN).
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", 517).
?DOC(" Add handler for media groups (albums of photos/videos)\n").
-spec on_media_group(
router(AMJU, AMJV),
fun((telega@bot:context(AMJU, AMJV), binary(), list(telega@model@types:message())) -> {ok,
telega@bot:context(AMJU, AMJV)} |
{error, AMJV})
) -> router(AMJU, AMJV).
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", 529).
?DOC(" Add a custom route with matcher function\n").
-spec on_custom(
router(AMKC, AMKD),
fun((telega@update:update()) -> boolean()),
fun((telega@bot:context(AMKC, AMKD), telega@update:update()) -> {ok,
telega@bot:context(AMKC, AMKD)} |
{error, AMKD})
) -> router(AMKC, AMKD).
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", 542).
?DOC(" Add a filtered route\n").
-spec on_filtered(
router(AMKK, AMKL),
filter(),
fun((telega@bot:context(AMKK, AMKL), telega@update:update()) -> {ok,
telega@bot:context(AMKK, AMKL)} |
{error, AMKL})
) -> router(AMKK, AMKL).
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", 555).
?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", 560).
?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", 570).
?DOC(" Combine two filters with AND logic\n").
-spec and2(filter(), filter()) -> filter().
and2(Left, Right) ->
{'and', Left, Right}.
-file("src/telega/router.gleam", 575).
?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", 585).
?DOC(" Combine two filters with OR logic\n").
-spec or2(filter(), filter()) -> filter().
or2(Left, Right) ->
{'or', Left, Right}.
-file("src/telega/router.gleam", 590).
?DOC(" Negate a filter\n").
-spec 'not'(filter()) -> filter().
'not'(F) ->
{'not', F}.
-file("src/telega/router.gleam", 595).
?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", 605).
?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", 615).
?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", 625).
?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", 635).
?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", 645).
?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", 655).
?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", 671).
?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", 688).
?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", 704).
?DOC(" Filter for private chats\n").
-spec is_private_chat() -> filter().
is_private_chat() ->
filter(<<"is_private_chat"/utf8>>, fun(Update) -> case Update of
{text_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"private"/utf8>>} ->
true;
_ ->
false
end;
{command_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"private"/utf8>>} ->
true;
_ ->
false
end;
{photo_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"private"/utf8>>} ->
true;
_ ->
false
end;
{video_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"private"/utf8>>} ->
true;
_ ->
false
end;
{voice_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"private"/utf8>>} ->
true;
_ ->
false
end;
{audio_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"private"/utf8>>} ->
true;
_ ->
false
end;
_ ->
false
end end).
-file("src/telega/router.gleam", 723).
?DOC(" Filter for group chats\n").
-spec is_group_chat() -> filter().
is_group_chat() ->
filter(<<"is_group_chat"/utf8>>, fun(Update) -> case Update of
{text_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"group"/utf8>>} ->
true;
{some, <<"supergroup"/utf8>>} ->
true;
_ ->
false
end;
{command_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"group"/utf8>>} ->
true;
{some, <<"supergroup"/utf8>>} ->
true;
_ ->
false
end;
{photo_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"group"/utf8>>} ->
true;
{some, <<"supergroup"/utf8>>} ->
true;
_ ->
false
end;
{video_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"group"/utf8>>} ->
true;
{some, <<"supergroup"/utf8>>} ->
true;
_ ->
false
end;
{voice_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"group"/utf8>>} ->
true;
{some, <<"supergroup"/utf8>>} ->
true;
_ ->
false
end;
{audio_update, _, _, _, Message, _} ->
case erlang:element(3, erlang:element(10, Message)) of
{some, <<"group"/utf8>>} ->
true;
{some, <<"supergroup"/utf8>>} ->
true;
_ ->
false
end;
_ ->
false
end end).
-file("src/telega/router.gleam", 742).
?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", 752).
?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", 762).
?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", 772).
?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", 785).
?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", 795).
?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", 809).
?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", 821).
?DOC(" Set fallback handler for unmatched updates\n").
-spec fallback(
router(AMKV, AMKW),
fun((telega@bot:context(AMKV, AMKW), telega@update:update()) -> {ok,
telega@bot:context(AMKV, AMKW)} |
{error, AMKW})
) -> router(AMKV, AMKW).
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", 832).
?DOC(" Add middleware to the router\n").
-spec use_middleware(
router(AMLD, AMLE),
fun((fun((telega@bot:context(AMLD, AMLE), telega@update:update()) -> {ok,
telega@bot:context(AMLD, AMLE)} |
{error, AMLE})) -> fun((telega@bot:context(AMLD, AMLE), telega@update:update()) -> {ok,
telega@bot:context(AMLD, AMLE)} |
{error, AMLE}))
) -> router(AMLD, AMLE).
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", 844).
?DOC(" Add a catch handler to the router that handles errors from all routes\n").
-spec with_catch_handler(
router(AMLL, AMLM),
fun((AMLM) -> {ok, telega@bot:context(AMLL, AMLM)} | {error, AMLM})
) -> router(AMLL, AMLM).
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", 1004).
?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", 957).
?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(AMMY, AMMZ), router(AMMY, AMMZ)) -> router(AMMY, AMMZ).
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", 989).
?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(AMNG, AMNH))) -> router(AMNG, AMNH).
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", 1024).
?DOC(" Merge a list of routers into a single flat router\n").
-spec merge_routers(list(router(AMNX, AMNY)), binary()) -> router(AMNX, AMNY).
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 => <<?FILEPATH/utf8>>,
module => <<"telega/router"/utf8>>,
function => <<"merge_routers"/utf8>>,
line => 1066,
value => _assert_fail,
start => 31062,
'end' => 31324,
pattern_start => 31073,
pattern_end => 31316})
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 => <<?FILEPATH/utf8>>,
module => <<"telega/router"/utf8>>,
function => <<"merge_routers"/utf8>>,
line => 1076,
value => _assert_fail@1,
start => 31332,
'end' => 31594,
pattern_start => 31343,
pattern_end => 31580})
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", 1012).
?DOC(" Convert any router (including ComposedRouter) to a flat Router with all routes merged\n").
-spec to_flat_router(router(AMNR, AMNS)) -> router(AMNR, AMNS).
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", 900).
?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(AMMQ, AMMR), router(AMMQ, AMMR)) -> router(AMMQ, AMMR).
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 => <<?FILEPATH/utf8>>,
module => <<"telega/router"/utf8>>,
function => <<"merge"/utf8>>,
line => 908,
value => _assert_fail,
start => 26479,
'end' => 26728,
pattern_start => 26490,
pattern_end => 26715})
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 => <<?FILEPATH/utf8>>,
module => <<"telega/router"/utf8>>,
function => <<"merge"/utf8>>,
line => 918,
value => _assert_fail@1,
start => 26732,
'end' => 26989,
pattern_start => 26743,
pattern_end => 26975})
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),
<<<<First_name@1/binary, "+"/utf8>>/binary, Second_name@1/binary>>}.
-file("src/telega/router.gleam", 1209).
?DOC(" Create a sub-router that processes updates within its own scope\n").
-spec scope(router(AMOI, AMOJ), fun((telega@update:update()) -> boolean())) -> router(AMOI, AMOJ).
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};
{custom_route, Matcher, Handler@7} ->
{custom_route,
fun(Update@1) ->
Predicate(Update@1) andalso Matcher(Update@1)
end,
Handler@7};
{filtered_route, F, Handler@8} ->
{filtered_route,
and2(filter(<<"scope"/utf8>>, Predicate), F),
Handler@8}
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,
<<Name/binary, "_scoped"/utf8>>};
{composed_router, _, _} = Composed ->
Composed
end.
-file("src/telega/router.gleam", 1350).
?DOC(" Find a command handler\n").
-spec find_command_handler(router(AMPH, AMPI), telega@update:update()) -> gleam@option:option(fun((telega@bot:context(AMPH, AMPI), telega@update:update()) -> {ok,
telega@bot:context(AMPH, AMPI)} |
{error, AMPI})).
find_command_handler(Router, Update) ->
case {Router, Update} of
{{router, Commands, _, _, _, _, _, _},
{command_update, _, _, Command, _, _}} ->
_pipe = gleam_stdlib:map_get(Commands, erlang:element(3, Command)),
gleam@option:from_result(_pipe);
{_, _} ->
none
end.
-file("src/telega/router.gleam", 1407).
?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", 1390).
?DOC(" Find callback handler by pattern matching\n").
-spec find_callback_by_pattern(
gleam@dict:dict(binary(), fun((telega@bot:context(AMQE, AMQF), telega@update:update()) -> {ok,
telega@bot:context(AMQE, AMQF)} |
{error, AMQF})),
binary()
) -> gleam@option:option(fun((telega@bot:context(AMQE, AMQF), telega@update:update()) -> {ok,
telega@bot:context(AMQE, AMQF)} |
{error, AMQF})).
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", 1378).
?DOC(" Find callback handler by data string\n").
-spec find_callback_by_data(
gleam@dict:dict(binary(), fun((telega@bot:context(AMPV, AMPW), telega@update:update()) -> {ok,
telega@bot:context(AMPV, AMPW)} |
{error, AMPW})),
binary()
) -> gleam@option:option(fun((telega@bot:context(AMPV, AMPW), telega@update:update()) -> {ok,
telega@bot:context(AMPV, AMPW)} |
{error, AMPW})).
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", 1363).
?DOC(" Find a callback handler\n").
-spec find_callback_handler(router(AMPO, AMPP), telega@update:update()) -> gleam@option:option(fun((telega@bot:context(AMPO, AMPP), telega@update:update()) -> {ok,
telega@bot:context(AMPO, AMPP)} |
{error, AMPP})).
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", 1513).
?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", 1116).
?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", 1497).
?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;
{{custom_route, Matcher, _}, _} ->
Matcher(Update);
{{filtered_route, Filter, _}, _} ->
evaluate_filter(Filter, Update);
{_, _} ->
false
end.
-file("src/telega/router.gleam", 1454).
?DOC(" Find matching route for an update\n").
-spec find_matching_route(list(route(AMRA, AMRB)), telega@update:update()) -> gleam@option:option(fun((telega@bot:context(AMRA, AMRB), telega@update:update()) -> {ok,
telega@bot:context(AMRA, AMRB)} |
{error, AMRB})).
find_matching_route(Routes, Update) ->
case Routes of
[] ->
none;
[Route | Rest] ->
case route_matches(Route, Update) of
true ->
Handler@8 = 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;
{{custom_route, _, Handler@6}, _} ->
Handler@6;
{{filtered_route, _, Handler@7}, _} ->
Handler@7;
{_, _} ->
fun(Ctx@6, _) -> {ok, Ctx@6} end
end,
{some, Handler@8};
false ->
find_matching_route(Rest, Update)
end
end.
-file("src/telega/router.gleam", 1523).
?DOC(" Apply middleware to a handler\n").
-spec apply_middleware(
fun((telega@bot:context(AMRM, AMRN), telega@update:update()) -> {ok,
telega@bot:context(AMRM, AMRN)} |
{error, AMRN}),
list(fun((fun((telega@bot:context(AMRM, AMRN), telega@update:update()) -> {ok,
telega@bot:context(AMRM, AMRN)} |
{error, AMRN})) -> fun((telega@bot:context(AMRM, AMRN), telega@update:update()) -> {ok,
telega@bot:context(AMRM, AMRN)} |
{error, AMRN})))
) -> fun((telega@bot:context(AMRM, AMRN), telega@update:update()) -> {ok,
telega@bot:context(AMRM, AMRN)} |
{error, AMRN}).
apply_middleware(Handler, Middleware) ->
gleam@list:fold(Middleware, Handler, fun(H, Mw) -> Mw(H) end).
-file("src/telega/router.gleam", 1531).
?DOC(" Logging middleware - logs update processing\n").
-spec with_logging(
fun((telega@bot:context(AMRV, AMRW), telega@update:update()) -> {ok,
telega@bot:context(AMRV, AMRW)} |
{error, AMRW})
) -> fun((telega@bot:context(AMRV, AMRW), telega@update:update()) -> {ok,
telega@bot:context(AMRV, AMRW)} |
{error, AMRW}).
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", 1552).
?DOC(" Filter middleware - only process updates that match predicate\n").
-spec with_filter(
fun((telega@update:update()) -> boolean()),
fun((telega@bot:context(AMSB, AMSC), telega@update:update()) -> {ok,
telega@bot:context(AMSB, AMSC)} |
{error, AMSC})
) -> fun((telega@bot:context(AMSB, AMSC), telega@update:update()) -> {ok,
telega@bot:context(AMSB, AMSC)} |
{error, AMSC}).
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", 1565).
?DOC(" Error recovery middleware\n").
-spec with_recovery(
fun((AMSH) -> {ok, telega@bot:context(AMSI, AMSH)} | {error, AMSH}),
fun((telega@bot:context(AMSI, AMSH), telega@update:update()) -> {ok,
telega@bot:context(AMSI, AMSH)} |
{error, AMSH})
) -> fun((telega@bot:context(AMSI, AMSH), telega@update:update()) -> {ok,
telega@bot:context(AMSI, AMSH)} |
{error, AMSH}).
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", 881).
?DOC(" Handle update through a list of composed routers\n").
-spec handle_composed(
list(router(AMMF, AMMG)),
telega@bot:context(AMMF, AMMG),
telega@update:update()
) -> {ok, telega@bot:context(AMMF, AMMG)} | {error, AMMG}.
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", 855).
?DOC(" Process an update through the router\n").
-spec handle(
router(AMLV, AMLW),
telega@bot:context(AMLV, AMLW),
telega@update:update()
) -> {ok, telega@bot:context(AMLV, AMLW)} | {error, AMLW}.
handle(Router, Ctx, Update) ->
case Router of
{router, _, _, _, _, Middleware, Catch_handler, _} ->
Handler = find_handler(Router, Update),
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", 1315).
?DOC(" Find handler in composed routers\n").
-spec find_handler_in_composed(list(router(AMOU, AMOV)), telega@update:update()) -> fun((telega@bot:context(AMOU, AMOV), telega@update:update()) -> {ok,
telega@bot:context(AMOU, AMOV)} |
{error, AMOV}).
find_handler_in_composed(Routers, Update) ->
case Routers of
[] ->
fun(Ctx, _) -> {ok, Ctx} end;
[Router | Rest] ->
case can_handle_update(Router, Update) of
true ->
find_handler(Router, Update);
false ->
find_handler_in_composed(Rest, Update)
end
end.
-file("src/telega/router.gleam", 1304).
?DOC(" Find the appropriate handler for an update\n").
-spec find_handler(router(AMOO, AMOP), telega@update:update()) -> fun((telega@bot:context(AMOO, AMOP), telega@update:update()) -> {ok,
telega@bot:context(AMOO, AMOP)} |
{error, AMOP}).
find_handler(Router, Update) ->
case Router of
{router, _, _, _, _, _, _, _} ->
find_handler_in_router(Router, Update);
{composed_router, Composes, _} ->
find_handler_in_composed(Composes, Update)
end.
-file("src/telega/router.gleam", 1438).
?DOC(" Find route or fallback in composed routers\n").
-spec find_route_or_fallback_in_composed(
list(router(AMQT, AMQU)),
telega@update:update()
) -> fun((telega@bot:context(AMQT, AMQU), telega@update:update()) -> {ok,
telega@bot:context(AMQT, AMQU)} |
{error, AMQU}).
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", 1417).
?DOC(" Try routes, then fallback\n").
-spec find_route_or_fallback(router(AMQN, AMQO), telega@update:update()) -> fun((telega@bot:context(AMQN, AMQO), telega@update:update()) -> {ok,
telega@bot:context(AMQN, AMQO)} |
{error, AMQO}).
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", 1332).
?DOC(" Find handler in a regular router (not composed)\n").
-spec find_handler_in_router(router(AMPB, AMPC), telega@update:update()) -> fun((telega@bot:context(AMPB, AMPC), telega@update:update()) -> {ok,
telega@bot:context(AMPB, AMPC)} |
{error, AMPC}).
find_handler_in_router(Router, Update) ->
case Update of
{command_update, _, _, _, _, _} ->
_pipe = find_command_handler(Router, Update),
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.