-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(AECI, AECJ, AECK) :: {bot, gleam@erlang@process:subject(bot_message()), telega@internal@config:config(), telega@model@types:user(), fun((context(AECI, AECJ, AECK), AECJ) -> {ok, nil} | {error, AECJ}), session_settings(AECI, AECJ), AECK, fun((context(AECI, AECJ, AECK), telega@update:update()) -> {ok, context(AECI, AECJ, AECK)} | {error, AECJ}), list(fun((pre_context(AECK)) -> pre_router_result())), telega@internal@registry:registry(chat_instance_message(AECI, AECJ, AECK)), gleam@otp@factory_supervisor:supervisor(chat_instance_args(AECI, AECJ, AECK), gleam@erlang@process:subject(chat_instance_message(AECI, AECJ, AECK))), boolean(), integer(), boolean(), integer(), gleam@option:option(gleam@erlang@process:subject(integer()))}. -type chat_instance_args(AECL, AECM, AECN) :: {chat_instance_args, binary(), telega@internal@config:config(), session_settings(AECL, AECM), fun((context(AECL, AECM, AECN), AECM) -> {ok, nil} | {error, AECM}), AECN, fun((context(AECL, AECM, AECN), telega@update:update()) -> {ok, context(AECL, AECM, AECN)} | {error, AECM}), telega@model@types:user(), telega@internal@registry:registry(chat_instance_message(AECL, AECM, AECN)), gleam@erlang@process:subject(bot_message())}. -type pre_context(AECO) :: {pre_context, telega@update:update(), telega@internal@config:config(), AECO, 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(AECP, AECQ, AECR) :: {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(AECP, AECQ, AECR), gleam@option:option(handler(AECP, AECQ, AECR)), gleam@option:option(integer())}. -type continuation(AECS, AECT, AECU) :: {continuation, handler(AECS, AECT, AECU), gleam@option:option(handler(AECS, AECT, AECU)), gleam@option:option(gleam@time@timestamp:timestamp())}. -type chat_instance(AECV, AECW, AECX) :: {chat_instance, binary(), AECV, AECX, telega@internal@config:config(), session_settings(AECV, AECW), gleam@erlang@process:subject(chat_instance_message(AECV, AECW, AECX)), gleam@option:option(continuation(AECV, AECW, AECX)), fun((context(AECV, AECW, AECX), telega@update:update()) -> {ok, context(AECV, AECW, AECX)} | {error, AECW}), fun((context(AECV, AECW, AECX), AECW) -> {ok, nil} | {error, AECW}), telega@model@types:user(), gleam@erlang@process:subject(bot_message())}. -type context(AECY, AECZ, AEDA) :: {context, binary(), telega@update:update(), telega@internal@config:config(), AECY, AEDA, gleam@erlang@process:subject(chat_instance_message(AECY, AECZ, AEDA)), gleam@option:option(gleam@time@timestamp:timestamp()), gleam@option:option(binary()), telega@model@types:user()}. -type session_settings(AEDB, AEDC) :: {session_settings, fun((binary(), AEDB) -> {ok, AEDB} | {error, AEDC}), fun((binary()) -> {ok, gleam@option:option(AEDB)} | {error, AEDC}), fun(() -> AEDB)}. -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(AEDD, AEDE, AEDF) :: {handle_all, fun((context(AEDD, AEDE, AEDF), telega@update:update()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_command, binary(), fun((context(AEDD, AEDE, AEDF), telega@update:command()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_commands, list(binary()), fun((context(AEDD, AEDE, AEDF), telega@update:command()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_text, fun((context(AEDD, AEDE, AEDF), binary()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_hears, hears(), fun((context(AEDD, AEDE, AEDF), binary()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_message, fun((context(AEDD, AEDE, AEDF), telega@model@types:message()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_voice, fun((context(AEDD, AEDE, AEDF), telega@model@types:voice()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_audio, fun((context(AEDD, AEDE, AEDF), telega@model@types:audio()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_video, fun((context(AEDD, AEDE, AEDF), telega@model@types:video()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_photos, fun((context(AEDD, AEDE, AEDF), list(telega@model@types:photo_size())) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_web_app_data, fun((context(AEDD, AEDE, AEDF), telega@model@types:web_app_data()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_callback_query, callback_query_filter(), fun((context(AEDD, AEDE, AEDF), binary(), binary()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})} | {handle_chat_member, fun((context(AEDD, AEDE, AEDF), telega@model@types:chat_member_updated()) -> {ok, context(AEDD, AEDE, AEDF)} | {error, AEDE})}. -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(AEGI)) -> pre_router_result())), pre_context(AEGI) ) -> 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(AEFR, AEFS, AEFT), bot_message()) -> gleam@otp@actor:next(bot(AEFR, AEFS, AEFT), 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(AEEJ, AEEK, AEEL)), telega@internal@config:config(), telega@model@types:user(), fun((context(AEEJ, AEEK, AEEL), telega@update:update()) -> {ok, context(AEEJ, AEEK, AEEL)} | {error, AEEK}), list(fun((pre_context(AEEL)) -> pre_router_result())), session_settings(AEEJ, AEEK), fun((context(AEEJ, AEEK, AEEL), AEEK) -> {ok, nil} | {error, AEEK}), AEEL, gleam@otp@factory_supervisor:supervisor(chat_instance_args(AEEJ, AEEK, AEEL), gleam@erlang@process:subject(chat_instance_message(AEEJ, AEEK, AEEL))), 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(AEJD, AEJE, AEJF), telega@update:update()) -> context(AEJD, AEJE, AEJF). 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(AEJN, AEJO, AEJP), telega@update:update(), gleam@option:option(gleam@erlang@process:subject(telega@webhook_reply:envelope_message())) ) -> context(AEJN, AEJO, AEJP). 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(AFPW, AFPX, AFPY), telega@update:update(), handler(AFPW, AFPX, AFPY) ) -> gleam@option:option({ok, context(AFPW, AFPX, AFPY)} | {error, AFPX}). 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(AELO, AELP, AELQ), telega@update:update(), handler(AELO, AELP, AELQ) ) -> gleam@option:option({ok, context(AELO, AELP, AELQ)} | {error, AELP}). 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(AEIP, AEIQ, AEIR), continuation(AEIP, AEIQ, AEIR), telega@update:update(), gleam@erlang@process:subject(boolean()), chat_instance(AEIP, AEIQ, AEIR) ) -> gleam@otp@actor:next(chat_instance(AEIP, AEIQ, AEIR), 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(AEHO, AEHP, AEHQ), chat_instance(AEHO, AEHP, AEHQ), telega@update:update(), gleam@erlang@process:subject(boolean()) ) -> gleam@otp@actor:next(chat_instance(AEHO, AEHP, AEHQ), 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(AEHG, AEHH, AEHI), chat_instance_message(AEHG, AEHH, AEHI) ) -> gleam@otp@actor:next(chat_instance(AEHG, AEHH, AEHI), 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(AEGW, AEGX, AEGY)) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(chat_instance_message(AEGW, AEGX, AEGY)))} | {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(AEJY, AEJZ, AEKA), AEJY) -> {ok, context(AEJY, AEJZ, AEKA)} | {error, AEJZ}. 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(AEKK, AEKL), telega@update:update()) -> {ok, gleam@option:option(AEKK)} | {error, AEKL}. 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(AEKV, AEKW, AEKX), handler(AEKV, AEKW, AEKX), gleam@option:option(handler(AEKV, AEKW, AEKX)), gleam@option:option(integer()) ) -> {ok, context(AEKV, AEKW, AEKX)} | {error, AEKW}. 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}.