Current section
Files
Jump to
Current section
Files
src/telega@bot.erl
-module(telega@bot).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega/bot.gleam").
-export([start/10, cancel_conversation/2, drain/2, is_draining/1, start_chat_instance/1, next_session/2, get_session/2, handle_update/2, dispatch_update_with_envelope/4, wait_handler/4]).
-export_type([bot/3, chat_instance_args/3, pre_context/1, pre_router_result/0, bot_message/0, chat_instance_message/3, continuation/3, chat_instance/3, context/3, session_settings/2, callback_query_filter/0, hears/0, handler/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Core bot actor and chat instance management.\n"
"\n"
" This module implements the actor-based architecture for handling Telegram updates.\n"
" It contains the `Bot` actor (the central dispatcher) and `ChatInstance` actors\n"
" (one per unique `{chat_id}:{from_id}` combination).\n"
"\n"
" ## Supervision tree\n"
"\n"
" Both the `Bot` actor and `ChatInstance` actors run inside a supervision tree\n"
" created by `telega.init()` or `telega.init_for_polling()`:\n"
"\n"
" ```text\n"
" TelegaRootSupervisor (static_supervisor, OneForOne)\n"
" ├── ChatInstances (factory_supervisor, Transient children)\n"
" │ ├── ChatInstance {chat1:user1}\n"
" │ ├── ChatInstance {chat2:user2}\n"
" │ └── ...\n"
" ├── Bot actor (worker, Permanent)\n"
" └── Polling worker (worker, Permanent) — only for polling mode\n"
" ```\n"
"\n"
" - The `Bot` actor is a `Permanent` worker — it always restarts on crash.\n"
" - `ChatInstance` actors are `Transient` — they restart only on abnormal exit,\n"
" not on normal shutdown. On restart a `ChatInstance` re-registers itself in\n"
" the ETS registry, overwriting the stale subject.\n"
" - The `Bot` creates new `ChatInstance` actors via `factory_supervisor.start_child`,\n"
" which ensures they are supervised from the moment they start.\n"
"\n"
" ## Handler pattern\n"
"\n"
" All handlers follow this signature:\n"
"\n"
" ```gleam\n"
" fn handler(ctx: Context(session, error, dependencies), data: Type) -> Result(Context(session, error, dependencies), error)\n"
" ```\n"
"\n"
" Always return the updated context — it carries the (potentially modified) session.\n"
"\n"
" ## Conversation API\n"
"\n"
" The `wait_handler` function and the `Handler` type enable multi-message\n"
" conversations: the chat instance suspends its main handler and waits for a\n"
" specific update type. See `telega.wait_text`, `telega.wait_command`, etc.\n"
).
-opaque bot(AHBL, AHBM, AHBN) :: {bot,
gleam@erlang@process:subject(bot_message()),
telega@internal@config:config(),
telega@model@types:user(),
fun((context(AHBL, AHBM, AHBN), AHBM) -> {ok, nil} | {error, AHBM}),
session_settings(AHBL, AHBM),
AHBN,
fun((context(AHBL, AHBM, AHBN), telega@update:update()) -> {ok,
context(AHBL, AHBM, AHBN)} |
{error, AHBM}),
list(fun((pre_context(AHBN)) -> pre_router_result())),
telega@internal@registry:registry(chat_instance_message(AHBL, AHBM, AHBN)),
gleam@otp@factory_supervisor:supervisor(chat_instance_args(AHBL, AHBM, AHBN), gleam@erlang@process:subject(chat_instance_message(AHBL, AHBM, AHBN))),
boolean(),
integer(),
boolean(),
integer(),
gleam@option:option(gleam@erlang@process:subject(integer()))}.
-type chat_instance_args(AHBO, AHBP, AHBQ) :: {chat_instance_args,
binary(),
telega@internal@config:config(),
session_settings(AHBO, AHBP),
fun((context(AHBO, AHBP, AHBQ), AHBP) -> {ok, nil} | {error, AHBP}),
AHBQ,
fun((context(AHBO, AHBP, AHBQ), telega@update:update()) -> {ok,
context(AHBO, AHBP, AHBQ)} |
{error, AHBP}),
telega@model@types:user(),
telega@internal@registry:registry(chat_instance_message(AHBO, AHBP, AHBQ)),
gleam@erlang@process:subject(bot_message())}.
-type pre_context(AHBR) :: {pre_context,
telega@update:update(),
telega@internal@config:config(),
AHBR,
telega@model@types:user()}.
-type pre_router_result() :: continue | stop.
-opaque bot_message() :: {cancel_conversation_bot_message, binary()} |
{handle_update_bot_message,
telega@update:update(),
gleam@erlang@process:subject(boolean()),
gleam@option:option(gleam@erlang@process:subject(telega@webhook_reply:envelope_message()))} |
update_handled_bot_message |
{start_drain_bot_message, gleam@erlang@process:subject(integer())} |
{is_draining_bot_message, gleam@erlang@process:subject(boolean())}.
-opaque chat_instance_message(AHBS, AHBT, AHBU) :: {handle_new_chat_instance_message,
telega@update:update(),
gleam@erlang@process:subject(boolean()),
gleam@option:option(gleam@erlang@process:subject(telega@webhook_reply:envelope_message()))} |
{wait_handler_chat_instance_message,
handler(AHBS, AHBT, AHBU),
gleam@option:option(handler(AHBS, AHBT, AHBU)),
gleam@option:option(integer())}.
-type continuation(AHBV, AHBW, AHBX) :: {continuation,
handler(AHBV, AHBW, AHBX),
gleam@option:option(handler(AHBV, AHBW, AHBX)),
gleam@option:option(gleam@time@timestamp:timestamp())}.
-type chat_instance(AHBY, AHBZ, AHCA) :: {chat_instance,
binary(),
AHBY,
AHCA,
telega@internal@config:config(),
session_settings(AHBY, AHBZ),
gleam@erlang@process:subject(chat_instance_message(AHBY, AHBZ, AHCA)),
gleam@option:option(continuation(AHBY, AHBZ, AHCA)),
fun((context(AHBY, AHBZ, AHCA), telega@update:update()) -> {ok,
context(AHBY, AHBZ, AHCA)} |
{error, AHBZ}),
fun((context(AHBY, AHBZ, AHCA), AHBZ) -> {ok, nil} | {error, AHBZ}),
telega@model@types:user(),
gleam@erlang@process:subject(bot_message())}.
-type context(AHCB, AHCC, AHCD) :: {context,
binary(),
telega@update:update(),
telega@internal@config:config(),
AHCB,
AHCD,
gleam@erlang@process:subject(chat_instance_message(AHCB, AHCC, AHCD)),
gleam@option:option(gleam@time@timestamp:timestamp()),
gleam@option:option(binary()),
telega@model@types:user()}.
-type session_settings(AHCE, AHCF) :: {session_settings,
fun((binary(), AHCE) -> {ok, AHCE} | {error, AHCF}),
fun((binary()) -> {ok, gleam@option:option(AHCE)} | {error, AHCF}),
fun(() -> AHCE)}.
-type callback_query_filter() :: {callback_query_filter, gleam@regexp:regexp()}.
-type hears() :: {hear_text, binary()} |
{hear_texts, list(binary())} |
{hear_regex, gleam@regexp:regexp()} |
{hear_regexes, list(gleam@regexp:regexp())}.
-type handler(AHCG, AHCH, AHCI) :: {handle_all,
fun((context(AHCG, AHCH, AHCI), telega@update:update()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_command,
binary(),
fun((context(AHCG, AHCH, AHCI), telega@update:command()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_commands,
list(binary()),
fun((context(AHCG, AHCH, AHCI), telega@update:command()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_text,
fun((context(AHCG, AHCH, AHCI), binary()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_hears,
hears(),
fun((context(AHCG, AHCH, AHCI), binary()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_message,
fun((context(AHCG, AHCH, AHCI), telega@model@types:message()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_voice,
fun((context(AHCG, AHCH, AHCI), telega@model@types:voice()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_audio,
fun((context(AHCG, AHCH, AHCI), telega@model@types:audio()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_video,
fun((context(AHCG, AHCH, AHCI), telega@model@types:video()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_photos,
fun((context(AHCG, AHCH, AHCI), list(telega@model@types:photo_size())) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_web_app_data,
fun((context(AHCG, AHCH, AHCI), telega@model@types:web_app_data()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_callback_query,
callback_query_filter(),
fun((context(AHCG, AHCH, AHCI), binary(), binary()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})} |
{handle_chat_member,
fun((context(AHCG, AHCH, AHCI), telega@model@types:chat_member_updated()) -> {ok,
context(AHCG, AHCH, AHCI)} |
{error, AHCH})}.
-file("src/telega/bot.gleam", 853).
-spec build_session_key(telega@update:update()) -> binary().
build_session_key(Update) ->
<<<<(erlang:integer_to_binary(erlang:element(3, Update)))/binary, ":"/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(2, Update)))/binary>>.
-file("src/telega/bot.gleam", 388).
-spec handle_update_bot_message(
bot(any(), any(), any()),
telega@update:update(),
gleam@erlang@process:subject(boolean()),
gleam@option:option(gleam@erlang@process:subject(telega@webhook_reply:envelope_message()))
) -> {ok, nil} | {error, telega@error:telega_error()}.
handle_update_bot_message(Bot, Update, Reply_with, Envelope) ->
Key = build_session_key(Update),
case telega@internal@registry:get(erlang:element(10, Bot), Key) of
{some, Chat_subject} ->
_pipe = gleam@otp@actor:send(
Chat_subject,
{handle_new_chat_instance_message, Update, Reply_with, Envelope}
),
{ok, _pipe};
none ->
telega@telemetry:execute(
[<<"telega"/utf8>>, <<"chat_instance"/utf8>>, <<"spawn"/utf8>>],
[{<<"count"/utf8>>, 1}],
[{<<"chat_id"/utf8>>, {int_value, erlang:element(3, Update)}},
{<<"from_id"/utf8>>, {int_value, erlang:element(2, Update)}}]
),
Args = {chat_instance_args,
Key,
erlang:element(3, Bot),
erlang:element(6, Bot),
erlang:element(5, Bot),
erlang:element(7, Bot),
erlang:element(8, Bot),
erlang:element(4, Bot),
erlang:element(10, Bot),
erlang:element(2, Bot)},
gleam@result:'try'(
begin
_pipe@1 = gleam@otp@factory_supervisor:start_child(
erlang:element(11, Bot),
Args
),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {chat_instance_start_error, Field@0} end
)
end,
fun(Started) ->
_pipe@2 = gleam@otp@actor:send(
erlang:element(3, Started),
{handle_new_chat_instance_message,
Update,
Reply_with,
Envelope}
),
{ok, _pipe@2}
end
)
end.
-file("src/telega/bot.gleam", 374).
-spec do_run_pre_handlers(
list(fun((pre_context(AHFL)) -> pre_router_result())),
pre_context(AHFL)
) -> pre_router_result().
do_run_pre_handlers(Handlers, Pre_ctx) ->
case Handlers of
[] ->
continue;
[Handler | Rest] ->
case Handler(Pre_ctx) of
stop ->
stop;
continue ->
do_run_pre_handlers(Rest, Pre_ctx)
end
end.
-file("src/telega/bot.gleam", 355).
?DOC(
" Run the global pre-router middleware chain. Returns `Stop` as soon as one\n"
" of them stops the update, otherwise `Continue`.\n"
).
-spec run_pre_handlers(bot(any(), any(), any()), telega@update:update()) -> pre_router_result().
run_pre_handlers(Bot, Update) ->
case erlang:element(9, Bot) of
[] ->
continue;
Handlers ->
Pre_ctx = {pre_context,
Update,
erlang:element(3, Bot),
erlang:element(7, Bot),
erlang:element(4, Bot)},
do_run_pre_handlers(Handlers, Pre_ctx)
end.
-file("src/telega/bot.gleam", 255).
-spec bot_loop(bot(AHEU, AHEV, AHEW), bot_message()) -> gleam@otp@actor:next(bot(AHEU, AHEV, AHEW), bot_message()).
bot_loop(Bot, Message) ->
case Message of
{handle_update_bot_message, Update, Reply_with, Envelope} ->
case erlang:element(12, Bot) of
false ->
gleam@erlang@process:send(Reply_with, false),
gleam@otp@actor:continue(Bot);
true ->
case run_pre_handlers(Bot, Update) of
stop ->
gleam@erlang@process:send(Reply_with, true),
gleam@otp@actor:continue(Bot);
continue ->
case handle_update_bot_message(
Bot,
Update,
Reply_with,
Envelope
) of
{ok, _} ->
gleam@otp@actor:continue(
{bot,
erlang:element(2, Bot),
erlang:element(3, Bot),
erlang:element(4, Bot),
erlang:element(5, Bot),
erlang:element(6, Bot),
erlang:element(7, Bot),
erlang:element(8, Bot),
erlang:element(9, Bot),
erlang:element(10, Bot),
erlang:element(11, Bot),
erlang:element(12, Bot),
erlang:element(13, Bot) + 1,
erlang:element(14, Bot),
erlang:element(15, Bot),
erlang:element(16, Bot)}
);
{error, Error} ->
telega@internal@log:error_d(
<<"Error in handler: "/utf8>>,
Error
),
gleam@otp@actor:stop()
end
end
end;
update_handled_bot_message ->
In_flight = gleam@int:max(0, erlang:element(13, Bot) - 1),
case erlang:element(14, Bot) andalso (In_flight =:= 0) of
true ->
case erlang:element(16, Bot) of
{some, Waiter} ->
gleam@erlang@process:send(
Waiter,
erlang:element(15, Bot)
);
none ->
nil
end,
gleam@otp@actor:continue(
{bot,
erlang:element(2, Bot),
erlang:element(3, Bot),
erlang:element(4, Bot),
erlang:element(5, Bot),
erlang:element(6, Bot),
erlang:element(7, Bot),
erlang:element(8, Bot),
erlang:element(9, Bot),
erlang:element(10, Bot),
erlang:element(11, Bot),
erlang:element(12, Bot),
0,
erlang:element(14, Bot),
erlang:element(15, Bot),
none}
);
false ->
gleam@otp@actor:continue(
{bot,
erlang:element(2, Bot),
erlang:element(3, Bot),
erlang:element(4, Bot),
erlang:element(5, Bot),
erlang:element(6, Bot),
erlang:element(7, Bot),
erlang:element(8, Bot),
erlang:element(9, Bot),
erlang:element(10, Bot),
erlang:element(11, Bot),
erlang:element(12, Bot),
In_flight,
erlang:element(14, Bot),
erlang:element(15, Bot),
erlang:element(16, Bot)}
)
end;
{start_drain_bot_message, Reply_with@1} ->
Bot@1 = {bot,
erlang:element(2, Bot),
erlang:element(3, Bot),
erlang:element(4, Bot),
erlang:element(5, Bot),
erlang:element(6, Bot),
erlang:element(7, Bot),
erlang:element(8, Bot),
erlang:element(9, Bot),
erlang:element(10, Bot),
erlang:element(11, Bot),
false,
erlang:element(13, Bot),
true,
erlang:element(13, Bot),
erlang:element(16, Bot)},
case erlang:element(13, Bot@1) of
0 ->
gleam@erlang@process:send(Reply_with@1, 0),
gleam@otp@actor:continue(Bot@1);
_ ->
gleam@otp@actor:continue(
{bot,
erlang:element(2, Bot@1),
erlang:element(3, Bot@1),
erlang:element(4, Bot@1),
erlang:element(5, Bot@1),
erlang:element(6, Bot@1),
erlang:element(7, Bot@1),
erlang:element(8, Bot@1),
erlang:element(9, Bot@1),
erlang:element(10, Bot@1),
erlang:element(11, Bot@1),
erlang:element(12, Bot@1),
erlang:element(13, Bot@1),
erlang:element(14, Bot@1),
erlang:element(15, Bot@1),
{some, Reply_with@1}}
)
end;
{is_draining_bot_message, Reply_with@2} ->
gleam@erlang@process:send(Reply_with@2, erlang:element(14, Bot)),
gleam@otp@actor:continue(Bot);
{cancel_conversation_bot_message, Key} ->
telega@internal@registry:unregister(erlang:element(10, Bot), Key),
gleam@otp@actor:continue(Bot)
end.
-file("src/telega/bot.gleam", 197).
-spec start(
telega@internal@registry:registry(chat_instance_message(AHDM, AHDN, AHDO)),
telega@internal@config:config(),
telega@model@types:user(),
fun((context(AHDM, AHDN, AHDO), telega@update:update()) -> {ok,
context(AHDM, AHDN, AHDO)} |
{error, AHDN}),
list(fun((pre_context(AHDO)) -> pre_router_result())),
session_settings(AHDM, AHDN),
fun((context(AHDM, AHDN, AHDO), AHDN) -> {ok, nil} | {error, AHDN}),
AHDO,
gleam@otp@factory_supervisor:supervisor(chat_instance_args(AHDM, AHDN, AHDO), gleam@erlang@process:subject(chat_instance_message(AHDM, AHDN, AHDO))),
gleam@option:option(gleam@erlang@process:name(bot_message()))
) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(bot_message()))} |
{error, gleam@otp@actor:start_error()}.
start(
Registry,
Config,
Bot_info,
Router_handler,
Pre_handlers,
Session_settings,
Catch_handler,
Dependencies,
Chat_factory,
Name
) ->
Builder = begin
_pipe@3 = gleam@otp@actor:new_with_initialiser(
1000,
fun(Self) ->
_pipe = {bot,
Self,
Config,
Bot_info,
Catch_handler,
Session_settings,
Dependencies,
Router_handler,
Pre_handlers,
Registry,
Chat_factory,
true,
0,
false,
0,
none},
_pipe@1 = gleam@otp@actor:initialised(_pipe),
_pipe@2 = gleam@otp@actor:returning(_pipe@1, Self),
{ok, _pipe@2}
end
),
gleam@otp@actor:on_message(_pipe@3, fun bot_loop/2)
end,
Builder@1 = case Name of
{some, N} ->
gleam@otp@actor:named(Builder, N);
none ->
Builder
end,
gleam@otp@actor:start(Builder@1).
-file("src/telega/bot.gleam", 248).
?DOC(" Stops waiting for any handler for specific key (chat_id)\n").
-spec cancel_conversation(bot(any(), any(), any()), binary()) -> nil.
cancel_conversation(Bot, Key) ->
gleam@otp@actor:send(
erlang:element(2, Bot),
{cancel_conversation_bot_message, Key}
).
-file("src/telega/bot.gleam", 331).
?DOC(
" Begin a graceful drain of the bot.\n"
"\n"
" Stops accepting new updates and blocks until all in-flight updates finish or\n"
" `timeout` milliseconds elapse. Returns the number of updates that were\n"
" in-flight when the drain started, or `-1` if the timeout was reached before\n"
" draining completed.\n"
).
-spec drain(gleam@erlang@process:subject(bot_message()), integer()) -> integer().
drain(Bot_subject, Timeout) ->
Reply = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Bot_subject, {start_drain_bot_message, Reply}),
case gleam@erlang@process:'receive'(Reply, Timeout) of
{ok, Count} ->
Count;
{error, _} ->
-1
end.
-file("src/telega/bot.gleam", 344).
?DOC(
" Whether the bot is currently draining (no longer accepting new updates).\n"
"\n"
" Webhook adapters use this to answer `503` so Telegram retries the update\n"
" after the deploy instead of dropping it.\n"
).
-spec is_draining(gleam@erlang@process:subject(bot_message())) -> boolean().
is_draining(Bot_subject) ->
Reply = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Bot_subject, {is_draining_bot_message, Reply}),
case gleam@erlang@process:'receive'(Reply, 1000) of
{ok, Draining} ->
Draining;
{error, _} ->
false
end.
-file("src/telega/bot.gleam", 792).
-spec new_context(chat_instance(AHIG, AHIH, AHII), telega@update:update()) -> context(AHIG, AHIH, AHII).
new_context(Chat, Update) ->
{context,
erlang:element(2, Chat),
Update,
erlang:element(5, Chat),
erlang:element(3, Chat),
erlang:element(4, Chat),
erlang:element(7, Chat),
none,
none,
erlang:element(11, Chat)}.
-file("src/telega/bot.gleam", 812).
?DOC(
" Build the update's context; when the update was dispatched with a\n"
" webhook-reply envelope, wrap the API client in a per-update transformer so\n"
" the handler's first eligible call can be claimed for the webhook response.\n"
).
-spec new_context_with_envelope(
chat_instance(AHIQ, AHIR, AHIS),
telega@update:update(),
gleam@option:option(gleam@erlang@process:subject(telega@webhook_reply:envelope_message()))
) -> context(AHIQ, AHIR, AHIS).
new_context_with_envelope(Chat, Update, Envelope) ->
Context = new_context(Chat, Update),
case Envelope of
none ->
Context;
{some, Envelope@1} ->
Api_client = telega@client:use_transformer(
erlang:element(5, erlang:element(4, Context)),
telega@webhook_reply:transformer(Envelope@1)
),
{context,
erlang:element(2, Context),
erlang:element(3, Context),
begin
_record = erlang:element(4, Context),
{config,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
Api_client}
end,
erlang:element(5, Context),
erlang:element(6, Context),
erlang:element(7, Context),
erlang:element(8, Context),
erlang:element(9, Context),
erlang:element(10, Context)}
end.
-file("src/telega/bot.gleam", 641).
-spec stop_chat_instance(chat_instance(any(), any(), any()), binary()) -> gleam@otp@actor:next(any(), any()).
stop_chat_instance(Chat, Reason) ->
gleam@erlang@process:send(
erlang:element(12, Chat),
update_handled_bot_message
),
telega@telemetry:execute(
[<<"telega"/utf8>>, <<"chat_instance"/utf8>>, <<"terminate"/utf8>>],
[{<<"count"/utf8>>, 1}],
[{<<"key"/utf8>>, {string_value, erlang:element(2, Chat)}},
{<<"reason"/utf8>>, {string_value, Reason}}]
),
gleam@otp@actor:stop().
-file("src/telega/bot.gleam", 657).
?DOC(
" Reply to the update's caller and notify the owning bot that one in-flight\n"
" update has finished, so the in-flight counter stays accurate for draining.\n"
).
-spec ack(
chat_instance(any(), any(), any()),
gleam@erlang@process:subject(boolean()),
boolean()
) -> nil.
ack(Chat, Reply_with, Value) ->
gleam@erlang@process:send(Reply_with, Value),
gleam@erlang@process:send(
erlang:element(12, Chat),
update_handled_bot_message
).
-file("src/telega/bot.gleam", 633).
-spec update_telemetry_metadata(telega@update:update()) -> list({binary(),
telega@telemetry:value()}).
update_telemetry_metadata(Upd) ->
[{<<"update_type"/utf8>>, {string_value, telega@update:type_to_string(Upd)}},
{<<"chat_id"/utf8>>, {int_value, erlang:element(3, Upd)}},
{<<"from_id"/utf8>>, {int_value, erlang:element(2, Upd)}}].
-file("src/telega/bot.gleam", 1028).
-spec do_handle(
context(AIOZ, AIPA, AIPB),
telega@update:update(),
handler(AIOZ, AIPA, AIPB)
) -> gleam@option:option({ok, context(AIOZ, AIPA, AIPB)} | {error, AIPA}).
do_handle(Context, Update, Handler) ->
case {Handler, Update} of
{{handle_all, Handler@1}, _} ->
_pipe = Context,
_pipe@1 = Handler@1(_pipe, Update),
{some, _pipe@1};
{{handle_text, Handler@2}, {text_update, _, _, Text, _, _}} ->
_pipe@2 = Context,
_pipe@3 = Handler@2(_pipe@2, Text),
{some, _pipe@3};
{{handle_hears, _, Handler@3}, {text_update, _, _, Text@1, _, _}} ->
_pipe@4 = Context,
_pipe@5 = Handler@3(_pipe@4, Text@1),
{some, _pipe@5};
{{handle_command, _, Handler@4},
{command_update, _, _, Update_command, _, _}} ->
_pipe@6 = Context,
_pipe@7 = Handler@4(_pipe@6, Update_command),
{some, _pipe@7};
{{handle_commands, _, Handler@5},
{command_update, _, _, Update_command@1, _, _}} ->
_pipe@8 = Context,
_pipe@9 = Handler@5(_pipe@8, Update_command@1),
{some, _pipe@9};
{{handle_callback_query, _, Handler@6},
{callback_query_update, _, _, Query, _}} ->
gleam@option:map(
erlang:element(7, Query),
fun(Data) ->
Handler@6(Context, Data, erlang:element(2, Query))
end
);
{{handle_message, Handler@7}, {message_update, _, _, Message, _}} ->
_pipe@10 = Context,
_pipe@11 = Handler@7(_pipe@10, Message),
{some, _pipe@11};
{{handle_chat_member, Handler@8},
{chat_member_update, _, _, Chat_member_updated, _}} ->
_pipe@12 = Context,
_pipe@13 = Handler@8(_pipe@12, Chat_member_updated),
{some, _pipe@13};
{{handle_voice, Handler@9}, {voice_update, _, _, Voice, _, _}} ->
_pipe@14 = Context,
_pipe@15 = Handler@9(_pipe@14, Voice),
{some, _pipe@15};
{{handle_audio, Handler@10}, {audio_update, _, _, Audio, _, _}} ->
_pipe@16 = Context,
_pipe@17 = Handler@10(_pipe@16, Audio),
{some, _pipe@17};
{{handle_video, Handler@11}, {video_update, _, _, Video, _, _}} ->
_pipe@18 = Context,
_pipe@19 = Handler@11(_pipe@18, Video),
{some, _pipe@19};
{{handle_web_app_data, Handler@12},
{web_app_update, _, _, Web_app_data, _, _}} ->
_pipe@20 = Context,
_pipe@21 = Handler@12(_pipe@20, Web_app_data),
{some, _pipe@21};
{_, _} ->
none
end.
-file("src/telega/bot.gleam", 994).
?DOC(
" Same as `do_handle`, but wraps the handler invocation in a\n"
" `telega.update` start/stop/exception telemetry span.\n"
" A handler that did not match the update (`None`) emits `stop`.\n"
).
-spec do_handle_with_telemetry(
context(AHKR, AHKS, AHKT),
telega@update:update(),
handler(AHKR, AHKS, AHKT)
) -> gleam@option:option({ok, context(AHKR, AHKS, AHKT)} | {error, AHKS}).
do_handle_with_telemetry(Context, Upd, Handler) ->
Metadata = update_telemetry_metadata(Upd),
Started_at = erlang:monotonic_time(),
telega@telemetry:execute(
[<<"telega"/utf8>>, <<"update"/utf8>>, <<"start"/utf8>>],
[{<<"system_time"/utf8>>, erlang:system_time()}],
Metadata
),
Result = do_handle(Context, Upd, Handler),
Duration = erlang:monotonic_time() - Started_at,
case Result of
{some, {error, Error}} ->
telega@telemetry:execute(
[<<"telega"/utf8>>, <<"update"/utf8>>, <<"exception"/utf8>>],
[{<<"duration"/utf8>>, Duration}],
[{<<"error"/utf8>>, {string_value, gleam@string:inspect(Error)}} |
Metadata]
);
_ ->
telega@telemetry:execute(
[<<"telega"/utf8>>, <<"update"/utf8>>, <<"stop"/utf8>>],
[{<<"duration"/utf8>>, Duration}],
Metadata
)
end,
Result.
-file("src/telega/bot.gleam", 666).
-spec do_handle_continuation(
context(AHHS, AHHT, AHHU),
continuation(AHHS, AHHT, AHHU),
telega@update:update(),
gleam@erlang@process:subject(boolean()),
chat_instance(AHHS, AHHT, AHHU)
) -> gleam@otp@actor:next(chat_instance(AHHS, AHHT, AHHU), any()).
do_handle_continuation(Context, Continuation, Update, Reply_with, Chat) ->
case do_handle_with_telemetry(
Context,
Update,
erlang:element(2, Continuation)
) of
{some, {ok, {context, _, _, _, New_session, _, _, _, _, _}}} ->
case (erlang:element(2, erlang:element(6, Chat)))(
erlang:element(2, Chat),
New_session
) of
{ok, Persisted_session} ->
ack(Chat, Reply_with, true),
gleam@otp@actor:continue(
{chat_instance,
erlang:element(2, Chat),
Persisted_session,
erlang:element(4, Chat),
erlang:element(5, Chat),
erlang:element(6, Chat),
erlang:element(7, Chat),
none,
erlang:element(9, Chat),
erlang:element(10, Chat),
erlang:element(11, Chat),
erlang:element(12, Chat)}
);
{error, E} ->
case (erlang:element(10, Chat))(Context, E) of
{ok, _} ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat);
{error, E@1} ->
telega@internal@log:error_d(
<<"Error in session persistence after continuation: "/utf8>>,
E@1
),
stop_chat_instance(
Chat,
<<"session_persist_failed"/utf8>>
)
end
end;
{some, {error, E@2}} ->
case (erlang:element(10, Chat))(Context, E@2) of
{ok, _} ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat);
{error, E@3} ->
telega@internal@log:error_d(
<<"Error in catch handler: "/utf8>>,
E@3
),
stop_chat_instance(Chat, <<"catch_handler_failed"/utf8>>)
end;
none ->
case erlang:element(3, Continuation) of
{some, Handler} ->
case do_handle(Context, Update, Handler) of
{some,
{ok,
{context, _, _, _, New_session@1, _, _, _, _, _}}} ->
case (erlang:element(2, erlang:element(6, Chat)))(
erlang:element(2, Chat),
New_session@1
) of
{ok, Persisted_session@1} ->
ack(Chat, Reply_with, true),
gleam@otp@actor:continue(
{chat_instance,
erlang:element(2, Chat),
Persisted_session@1,
erlang:element(4, Chat),
erlang:element(5, Chat),
erlang:element(6, Chat),
erlang:element(7, Chat),
erlang:element(8, Chat),
erlang:element(9, Chat),
erlang:element(10, Chat),
erlang:element(11, Chat),
erlang:element(12, Chat)}
);
{error, E@4} ->
case (erlang:element(10, Chat))(
Context,
E@4
) of
{ok, _} ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat);
{error, E@5} ->
telega@internal@log:error_d(
<<"Error in session persistence after handle_else: "/utf8>>,
E@5
),
stop_chat_instance(
Chat,
<<"session_persist_failed"/utf8>>
)
end
end;
{some, {error, E@6}} ->
case (erlang:element(10, Chat))(Context, E@6) of
{ok, _} ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat);
{error, E@7} ->
telega@internal@log:error_d(
<<"Error in catch else handler: "/utf8>>,
E@7
),
stop_chat_instance(
Chat,
<<"catch_handler_failed"/utf8>>
)
end;
none ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat)
end;
none ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat)
end
end.
-file("src/telega/bot.gleam", 550).
-spec do_handle_new_chat_instance_message(
context(AHGR, AHGS, AHGT),
chat_instance(AHGR, AHGS, AHGT),
telega@update:update(),
gleam@erlang@process:subject(boolean())
) -> gleam@otp@actor:next(chat_instance(AHGR, AHGS, AHGT), any()).
do_handle_new_chat_instance_message(Context, Chat, Update, Reply_with) ->
case erlang:element(8, Chat) of
{some, Continuation} ->
case erlang:element(4, Continuation) of
{some, Ttl} ->
case gleam@time@timestamp:compare(
Ttl,
gleam@time@timestamp:system_time()
) of
lt ->
do_handle_new_chat_instance_message(
Context,
{chat_instance,
erlang:element(2, Chat),
erlang:element(3, Chat),
erlang:element(4, Chat),
erlang:element(5, Chat),
erlang:element(6, Chat),
erlang:element(7, Chat),
none,
erlang:element(9, Chat),
erlang:element(10, Chat),
erlang:element(11, Chat),
erlang:element(12, Chat)},
Update,
Reply_with
);
_ ->
do_handle_continuation(
Context,
Continuation,
Update,
Reply_with,
Chat
)
end;
none ->
do_handle_continuation(
Context,
Continuation,
Update,
Reply_with,
Chat
)
end;
none ->
case telega@telemetry:span(
[<<"telega"/utf8>>, <<"update"/utf8>>],
update_telemetry_metadata(Update),
fun() -> (erlang:element(9, Chat))(Context, Update) end
) of
{ok, {context, _, _, _, New_session, _, _, _, _, _}} ->
case (erlang:element(2, erlang:element(6, Chat)))(
erlang:element(2, Chat),
New_session
) of
{ok, Persisted_session} ->
ack(Chat, Reply_with, true),
gleam@otp@actor:continue(
{chat_instance,
erlang:element(2, Chat),
Persisted_session,
erlang:element(4, Chat),
erlang:element(5, Chat),
erlang:element(6, Chat),
erlang:element(7, Chat),
erlang:element(8, Chat),
erlang:element(9, Chat),
erlang:element(10, Chat),
erlang:element(11, Chat),
erlang:element(12, Chat)}
);
{error, E} ->
case (erlang:element(10, Chat))(Context, E) of
{ok, _} ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat);
{error, E@1} ->
telega@internal@log:error_d(
<<"Error in session persistence: "/utf8>>,
E@1
),
stop_chat_instance(
Chat,
<<"session_persist_failed"/utf8>>
)
end
end;
{error, E@2} ->
case (erlang:element(10, Chat))(Context, E@2) of
{ok, _} ->
ack(Chat, Reply_with, false),
gleam@otp@actor:continue(Chat);
{error, E@3} ->
telega@internal@log:error_d(
<<"Error in catch handler: "/utf8>>,
E@3
),
stop_chat_instance(
Chat,
<<"catch_handler_failed"/utf8>>
)
end
end
end.
-file("src/telega/bot.gleam", 524).
-spec loop_chat_instance(
chat_instance(AHGJ, AHGK, AHGL),
chat_instance_message(AHGJ, AHGK, AHGL)
) -> gleam@otp@actor:next(chat_instance(AHGJ, AHGK, AHGL), any()).
loop_chat_instance(Chat, Message) ->
case Message of
{handle_new_chat_instance_message, Update, Reply_with, Envelope} ->
do_handle_new_chat_instance_message(
new_context_with_envelope(Chat, Update, Envelope),
Chat,
Update,
Reply_with
);
{wait_handler_chat_instance_message, Handler, Handle_else, Timeout} ->
_pipe@2 = {chat_instance,
erlang:element(2, Chat),
erlang:element(3, Chat),
erlang:element(4, Chat),
erlang:element(5, Chat),
erlang:element(6, Chat),
erlang:element(7, Chat),
begin
_pipe@1 = {continuation,
Handler,
Handle_else,
begin
gleam@option:map(
Timeout,
fun(Timeout@1) ->
_pipe = gleam@time@timestamp:system_time(),
gleam@time@timestamp:add(
_pipe,
gleam@time@duration:seconds(Timeout@1)
)
end
)
end},
{some, _pipe@1}
end,
erlang:element(9, Chat),
erlang:element(10, Chat),
erlang:element(11, Chat),
erlang:element(12, Chat)},
gleam@otp@actor:continue(_pipe@2)
end.
-file("src/telega/bot.gleam", 482).
?DOC(
" Start a chat instance. Used as the template function for factory_supervisor.\n"
" Self-registers in the registry on start (handles both first start and restart after crash).\n"
).
-spec start_chat_instance(chat_instance_args(AHFZ, AHGA, AHGB)) -> {ok,
gleam@otp@actor:started(gleam@erlang@process:subject(chat_instance_message(AHFZ, AHGA, AHGB)))} |
{error, gleam@otp@actor:start_error()}.
start_chat_instance(Args) ->
Session@1 = case (erlang:element(3, erlang:element(4, Args)))(
erlang:element(2, Args)
) of
{ok, {some, Session}} ->
Session;
{ok, none} ->
(erlang:element(4, erlang:element(4, Args)))();
{error, Error} ->
telega@internal@log:warning(
<<<<<<"Failed to get session for key "/utf8,
(erlang:element(2, Args))/binary>>/binary,
", falling back to default: "/utf8>>/binary,
(gleam@string:inspect(Error))/binary>>
),
(erlang:element(4, erlang:element(4, Args)))()
end,
_pipe@2 = gleam@otp@actor:new_with_initialiser(
10,
fun(Subject) ->
Chat_instance = {chat_instance,
erlang:element(2, Args),
Session@1,
erlang:element(6, Args),
erlang:element(3, Args),
erlang:element(4, Args),
Subject,
none,
erlang:element(7, Args),
erlang:element(5, Args),
erlang:element(8, Args),
erlang:element(10, Args)},
telega@internal@registry:register(
erlang:element(9, Args),
erlang:element(2, Args),
Subject
),
_pipe = gleam@otp@actor:initialised(Chat_instance),
_pipe@1 = gleam@otp@actor:returning(_pipe, Subject),
{ok, _pipe@1}
end
),
_pipe@3 = gleam@otp@actor:on_message(_pipe@2, fun loop_chat_instance/2),
gleam@otp@actor:start(_pipe@3).
-file("src/telega/bot.gleam", 846).
-spec next_session(context(AHJB, AHJC, AHJD), AHJB) -> {ok,
context(AHJB, AHJC, AHJD)} |
{error, AHJC}.
next_session(Ctx, Session) ->
{ok,
{context,
erlang:element(2, Ctx),
erlang:element(3, Ctx),
erlang:element(4, Ctx),
Session,
erlang:element(6, Ctx),
erlang:element(7, Ctx),
erlang:element(8, Ctx),
erlang:element(9, Ctx),
erlang:element(10, Ctx)}}.
-file("src/telega/bot.gleam", 857).
-spec get_session(session_settings(AHJN, AHJO), telega@update:update()) -> {ok,
gleam@option:option(AHJN)} |
{error, AHJO}.
get_session(Session_settings, Update) ->
_pipe = Update,
_pipe@1 = build_session_key(_pipe),
(erlang:element(3, Session_settings))(_pipe@1).
-file("src/telega/bot.gleam", 868).
?DOC(false).
-spec handle_update(
gleam@erlang@process:subject(bot_message()),
telega@update:update()
) -> boolean().
handle_update(Bot_subject, Update) ->
gleam@erlang@process:call_forever(
Bot_subject,
fun(_capture) -> {handle_update_bot_message, Update, _capture, none} end
).
-file("src/telega/bot.gleam", 876).
?DOC(false).
-spec dispatch_update_with_envelope(
gleam@erlang@process:subject(bot_message()),
telega@update:update(),
gleam@erlang@process:subject(boolean()),
gleam@erlang@process:subject(telega@webhook_reply:envelope_message())
) -> nil.
dispatch_update_with_envelope(Bot_subject, Update, Reply_with, Envelope) ->
gleam@erlang@process:send(
Bot_subject,
{handle_update_bot_message, Update, Reply_with, {some, Envelope}}
).
-file("src/telega/bot.gleam", 978).
?DOC(
" Pass any handler to start waiting\n"
"\n"
" `or` - calls if there are any other updates\n"
" `timeout` - the conversation will be canceled after this timeout\n"
).
-spec wait_handler(
context(AHJY, AHJZ, AHKA),
handler(AHJY, AHJZ, AHKA),
gleam@option:option(handler(AHJY, AHJZ, AHKA)),
gleam@option:option(integer())
) -> {ok, context(AHJY, AHJZ, AHKA)} | {error, AHJZ}.
wait_handler(Ctx, Handler, Handle_else, Timeout) ->
gleam@otp@actor:send(
erlang:element(7, Ctx),
{wait_handler_chat_instance_message, Handler, Handle_else, Timeout}
),
{ok, Ctx}.