Current section

Files

Jump to
telega src telega@bot.erl
Raw

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(AHFJ, AHFK, AHFL) :: {bot,
gleam@erlang@process:subject(bot_message()),
telega@internal@config:config(),
telega@model@types:user(),
fun((context(AHFJ, AHFK, AHFL), AHFK) -> {ok, nil} | {error, AHFK}),
session_settings(AHFJ, AHFK),
AHFL,
fun((context(AHFJ, AHFK, AHFL), telega@update:update()) -> {ok,
context(AHFJ, AHFK, AHFL)} |
{error, AHFK}),
list(fun((pre_context(AHFL)) -> pre_router_result())),
telega@internal@registry:registry(chat_instance_message(AHFJ, AHFK, AHFL)),
gleam@otp@factory_supervisor:supervisor(chat_instance_args(AHFJ, AHFK, AHFL), gleam@erlang@process:subject(chat_instance_message(AHFJ, AHFK, AHFL))),
boolean(),
integer(),
boolean(),
integer(),
gleam@option:option(gleam@erlang@process:subject(integer()))}.
-type chat_instance_args(AHFM, AHFN, AHFO) :: {chat_instance_args,
binary(),
telega@internal@config:config(),
session_settings(AHFM, AHFN),
fun((context(AHFM, AHFN, AHFO), AHFN) -> {ok, nil} | {error, AHFN}),
AHFO,
fun((context(AHFM, AHFN, AHFO), telega@update:update()) -> {ok,
context(AHFM, AHFN, AHFO)} |
{error, AHFN}),
telega@model@types:user(),
telega@internal@registry:registry(chat_instance_message(AHFM, AHFN, AHFO)),
gleam@erlang@process:subject(bot_message())}.
-type pre_context(AHFP) :: {pre_context,
telega@update:update(),
telega@internal@config:config(),
AHFP,
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(AHFQ, AHFR, AHFS) :: {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(AHFQ, AHFR, AHFS),
gleam@option:option(handler(AHFQ, AHFR, AHFS)),
gleam@option:option(integer())}.
-type continuation(AHFT, AHFU, AHFV) :: {continuation,
handler(AHFT, AHFU, AHFV),
gleam@option:option(handler(AHFT, AHFU, AHFV)),
gleam@option:option(gleam@time@timestamp:timestamp())}.
-type chat_instance(AHFW, AHFX, AHFY) :: {chat_instance,
binary(),
AHFW,
AHFY,
telega@internal@config:config(),
session_settings(AHFW, AHFX),
gleam@erlang@process:subject(chat_instance_message(AHFW, AHFX, AHFY)),
gleam@option:option(continuation(AHFW, AHFX, AHFY)),
fun((context(AHFW, AHFX, AHFY), telega@update:update()) -> {ok,
context(AHFW, AHFX, AHFY)} |
{error, AHFX}),
fun((context(AHFW, AHFX, AHFY), AHFX) -> {ok, nil} | {error, AHFX}),
telega@model@types:user(),
gleam@erlang@process:subject(bot_message())}.
-type context(AHFZ, AHGA, AHGB) :: {context,
binary(),
telega@update:update(),
telega@internal@config:config(),
AHFZ,
AHGB,
gleam@erlang@process:subject(chat_instance_message(AHFZ, AHGA, AHGB)),
gleam@option:option(gleam@time@timestamp:timestamp()),
gleam@option:option(binary()),
telega@model@types:user()}.
-type session_settings(AHGC, AHGD) :: {session_settings,
fun((binary(), AHGC) -> {ok, AHGC} | {error, AHGD}),
fun((binary()) -> {ok, gleam@option:option(AHGC)} | {error, AHGD}),
fun(() -> AHGC)}.
-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(AHGE, AHGF, AHGG) :: {handle_all,
fun((context(AHGE, AHGF, AHGG), telega@update:update()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_command,
binary(),
fun((context(AHGE, AHGF, AHGG), telega@update:command()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_commands,
list(binary()),
fun((context(AHGE, AHGF, AHGG), telega@update:command()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_text,
fun((context(AHGE, AHGF, AHGG), binary()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_hears,
hears(),
fun((context(AHGE, AHGF, AHGG), binary()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_message,
fun((context(AHGE, AHGF, AHGG), telega@model@types:message()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_voice,
fun((context(AHGE, AHGF, AHGG), telega@model@types:voice()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_audio,
fun((context(AHGE, AHGF, AHGG), telega@model@types:audio()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_video,
fun((context(AHGE, AHGF, AHGG), telega@model@types:video()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_photos,
fun((context(AHGE, AHGF, AHGG), list(telega@model@types:photo_size())) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_web_app_data,
fun((context(AHGE, AHGF, AHGG), telega@model@types:web_app_data()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_callback_query,
callback_query_filter(),
fun((context(AHGE, AHGF, AHGG), binary(), binary()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})} |
{handle_chat_member,
fun((context(AHGE, AHGF, AHGG), telega@model@types:chat_member_updated()) -> {ok,
context(AHGE, AHGF, AHGG)} |
{error, AHGF})}.
-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(AHJJ)) -> pre_router_result())),
pre_context(AHJJ)
) -> 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(AHIS, AHIT, AHIU), bot_message()) -> gleam@otp@actor:next(bot(AHIS, AHIT, AHIU), 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(AHHK, AHHL, AHHM)),
telega@internal@config:config(),
telega@model@types:user(),
fun((context(AHHK, AHHL, AHHM), telega@update:update()) -> {ok,
context(AHHK, AHHL, AHHM)} |
{error, AHHL}),
list(fun((pre_context(AHHM)) -> pre_router_result())),
session_settings(AHHK, AHHL),
fun((context(AHHK, AHHL, AHHM), AHHL) -> {ok, nil} | {error, AHHL}),
AHHM,
gleam@otp@factory_supervisor:supervisor(chat_instance_args(AHHK, AHHL, AHHM), gleam@erlang@process:subject(chat_instance_message(AHHK, AHHL, AHHM))),
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(AHME, AHMF, AHMG), telega@update:update()) -> context(AHME, AHMF, AHMG).
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(AHMO, AHMP, AHMQ),
telega@update:update(),
gleam@option:option(gleam@erlang@process:subject(telega@webhook_reply:envelope_message()))
) -> context(AHMO, AHMP, AHMQ).
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(AISX, AISY, AISZ),
telega@update:update(),
handler(AISX, AISY, AISZ)
) -> gleam@option:option({ok, context(AISX, AISY, AISZ)} | {error, AISY}).
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(AHOP, AHOQ, AHOR),
telega@update:update(),
handler(AHOP, AHOQ, AHOR)
) -> gleam@option:option({ok, context(AHOP, AHOQ, AHOR)} | {error, AHOQ}).
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(AHLQ, AHLR, AHLS),
continuation(AHLQ, AHLR, AHLS),
telega@update:update(),
gleam@erlang@process:subject(boolean()),
chat_instance(AHLQ, AHLR, AHLS)
) -> gleam@otp@actor:next(chat_instance(AHLQ, AHLR, AHLS), 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(AHKP, AHKQ, AHKR),
chat_instance(AHKP, AHKQ, AHKR),
telega@update:update(),
gleam@erlang@process:subject(boolean())
) -> gleam@otp@actor:next(chat_instance(AHKP, AHKQ, AHKR), 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(AHKH, AHKI, AHKJ),
chat_instance_message(AHKH, AHKI, AHKJ)
) -> gleam@otp@actor:next(chat_instance(AHKH, AHKI, AHKJ), 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(AHJX, AHJY, AHJZ)) -> {ok,
gleam@otp@actor:started(gleam@erlang@process:subject(chat_instance_message(AHJX, AHJY, AHJZ)))} |
{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(AHMZ, AHNA, AHNB), AHMZ) -> {ok,
context(AHMZ, AHNA, AHNB)} |
{error, AHNA}.
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(AHNL, AHNM), telega@update:update()) -> {ok,
gleam@option:option(AHNL)} |
{error, AHNM}.
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(AHNW, AHNX, AHNY),
handler(AHNW, AHNX, AHNY),
gleam@option:option(handler(AHNW, AHNX, AHNY)),
gleam@option:option(integer())
) -> {ok, context(AHNW, AHNX, AHNY)} | {error, AHNX}.
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}.