-module(telega@bot). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([set_webhook/1, wait_handler/2, start_registry/4]). -export_type([registry/1, registry_message/0, session_settings/1, context/1, bot_instanse_message/1, bot_instanse/1, hears/0, handler/1, callback_query_filter/0]). -type registry(UCC) :: {registry, gleam@dict:dict(binary(), gleam@erlang@process:subject(bot_instanse_message(UCC))), telega@internal@config:config(), session_settings(UCC), list(handler(UCC)), gleam@erlang@process:subject(registry_message()), gleam@erlang@process:subject(gleam@erlang@process:subject(bot_instanse_message(UCC)))}. -type registry_message() :: {handle_bot_registry_message, telega@update:update()}. -type session_settings(UCD) :: {session_settings, fun((binary(), UCD) -> {ok, UCD} | {error, binary()}), fun((binary()) -> {ok, UCD} | {error, binary()})}. -type context(UCE) :: {context, binary(), telega@update:update(), telega@internal@config:config(), UCE, gleam@erlang@process:subject(bot_instanse_message(UCE))}. -type bot_instanse_message(UCF) :: bot_instanse_message_ok | {bot_instanse_message_new, gleam@erlang@process:subject(bot_instanse_message(UCF)), telega@update:update()} | {bot_instanse_message_wait_handler, handler(UCF)}. -type bot_instanse(UCG) :: {bot_instanse, binary(), UCG, telega@internal@config:config(), list(handler(UCG)), session_settings(UCG), gleam@option:option(handler(UCG)), gleam@erlang@process:subject(bot_instanse_message(UCG))}. -type hears() :: {hear_text, binary()} | {hear_texts, list(binary())} | {hear_regex, gleam@regex:regex()} | {hear_regexes, list(gleam@regex:regex())}. -type handler(UCH) :: {handle_all, fun((context(UCH)) -> {ok, UCH} | {error, binary()})} | {handle_command, binary(), fun((context(UCH), telega@update:command()) -> {ok, UCH} | {error, binary()})} | {handle_commands, list(binary()), fun((context(UCH), telega@update:command()) -> {ok, UCH} | {error, binary()})} | {handle_text, fun((context(UCH), binary()) -> {ok, UCH} | {error, binary()})} | {handle_hears, hears(), fun((context(UCH), binary()) -> {ok, UCH} | {error, binary()})} | {handle_callback_query, callback_query_filter(), fun((context(UCH), binary(), binary()) -> {ok, UCH} | {error, binary()})}. -type callback_query_filter() :: {callback_query_filter, gleam@regex:regex()}. -spec try_send_update( gleam@erlang@process:subject(bot_instanse_message(UCS)), telega@update:update() ) -> {ok, bot_instanse_message(UCS)} | {error, gleam@erlang@process:call_error(bot_instanse_message(UCS))}. try_send_update(Registry_item, Update) -> gleam@erlang@process:try_call( Registry_item, fun(_capture) -> {bot_instanse_message_new, _capture, Update} end, 1000 ). -spec set_webhook(telega@internal@config:config()) -> {ok, boolean()} | {error, binary()}. set_webhook(Config) -> telega@api:set_webhook( erlang:element(5, Config), {set_webhook_parameters, <<<<(erlang:element(2, Config))/binary, "/"/utf8>>/binary, (erlang:element(3, Config))/binary>>, none, none, none, none, {some, erlang:element(4, Config)}} ). -spec wait_handler(context(UDK), handler(UDK)) -> {ok, UDK} | {error, binary()}. wait_handler(Ctx, Handler) -> gleam@erlang@process:send( erlang:element(6, Ctx), {bot_instanse_message_wait_handler, Handler} ), {ok, erlang:element(5, Ctx)}. -spec new_context(bot_instanse(UDP), telega@update:update()) -> context(UDP). new_context(Bot, Update) -> {context, erlang:element(2, Bot), Update, erlang:element(4, Bot), erlang:element(3, Bot), erlang:element(8, Bot)}. -spec get_session_key(telega@update:update()) -> {ok, binary()} | {error, binary()}. get_session_key(Update) -> case Update of {command_update, Chat_id, _, _} -> {ok, gleam@int:to_string(Chat_id)}; {text_update, Chat_id@1, _, _} -> {ok, gleam@int:to_string(Chat_id@1)}; {callback_query_update, From_id, _} -> {ok, gleam@int:to_string(From_id)}; {unknown_update, _} -> {error, <<"Unknown update type don't allow to get session key"/utf8>>} end. -spec get_session(session_settings(UDU), telega@update:update()) -> {ok, UDU} | {error, binary()}. get_session(Session_settings, Update) -> gleam@result:'try'( get_session_key(Update), fun(Key) -> _pipe = (erlang:element(3, Session_settings))(Key), gleam@result:map_error( _pipe, fun(E) -> <<"Failed to get session:\n "/utf8, (gleam@string:inspect(E))/binary>> end ) end ). -spec hears_check(binary(), hears()) -> boolean(). hears_check(Text, Hear) -> case Hear of {hear_text, Str} -> Text =:= Str; {hear_texts, Strs} -> gleam@list:contains(Strs, Text); {hear_regex, Re} -> gleam@regex:check(Re, Text); {hear_regexes, Regexes} -> gleam@list:any( Regexes, fun(_capture) -> gleam@regex:check(_capture, Text) end ) end. -spec do_handle(bot_instanse(UEI), telega@update:update(), handler(UEI)) -> gleam@option:option({ok, UEI} | {error, binary()}). do_handle(Bot, Update, Handler) -> case {Handler, Update} of {{handle_all, Handle}, _} -> {some, Handle(new_context(Bot, Update))}; {{handle_text, Handle@1}, {text_update, _, Text, _}} -> {some, Handle@1(new_context(Bot, Update), Text)}; {{handle_hears, Hear, Handle@2}, {text_update, _, Text@1, _}} -> case hears_check(Text@1, Hear) of true -> {some, Handle@2(new_context(Bot, Update), Text@1)}; false -> none end; {{handle_command, Command, Handle@3}, {command_update, _, Update_command, _}} -> case erlang:element(3, Update_command) =:= Command of true -> {some, Handle@3(new_context(Bot, Update), Update_command)}; false -> none end; {{handle_commands, Commands, Handle@4}, {command_update, _, Update_command@1, _}} -> case gleam@list:contains( Commands, erlang:element(3, Update_command@1) ) of true -> {some, Handle@4(new_context(Bot, Update), Update_command@1)}; false -> none end; {{handle_callback_query, Filter, Handle@5}, {callback_query_update, _, Raw}} -> case erlang:element(7, Raw) of {some, Data} -> case gleam@regex:check(erlang:element(2, Filter), Data) of true -> {some, Handle@5( new_context(Bot, Update), Data, erlang:element(2, Raw) )}; false -> none end; none -> none end; {_, _} -> none end. -spec loop_handlers( bot_instanse(UEO), telega@update:update(), list(handler(UEO)) ) -> {ok, UEO} | {error, binary()}. loop_handlers(Bot, Update, Handlers) -> case Handlers of [Handler | Rest] -> case do_handle(Bot, Update, Handler) of {some, {ok, New_session}} -> loop_handlers( erlang:setelement(3, Bot, New_session), Update, Rest ); {some, {error, E}} -> {error, <<<<<<"Failed to handle message "/utf8, (gleam@string:inspect(Update))/binary>>/binary, ":\n"/utf8>>/binary, E/binary>>}; none -> loop_handlers(Bot, Update, Rest) end; [] -> (erlang:element(2, erlang:element(6, Bot)))( erlang:element(2, Bot), erlang:element(3, Bot) ) end. -spec handle_bot_instanse_message(bot_instanse_message(UEE), bot_instanse(UEE)) -> gleam@otp@actor:next(any(), bot_instanse(UEE)). handle_bot_instanse_message(Message, Bot) -> case Message of {bot_instanse_message_new, Client, Message@1} -> case erlang:element(7, Bot) of {some, Handler} -> case do_handle(Bot, Message@1, Handler) of {some, {ok, New_session}} -> gleam@otp@actor:send( Client, bot_instanse_message_ok ), gleam@otp@actor:continue( erlang:setelement( 7, erlang:setelement(3, Bot, New_session), none ) ); {some, {error, E}} -> telega@log:error( <<"Failed to handle update:\n"/utf8, E/binary>> ), {stop, normal}; none -> gleam@otp@actor:send( Client, bot_instanse_message_ok ), gleam@otp@actor:continue(Bot) end; none -> case loop_handlers(Bot, Message@1, erlang:element(5, Bot)) of {ok, New_session@1} -> gleam@otp@actor:send( Client, bot_instanse_message_ok ), gleam@otp@actor:continue( erlang:setelement(3, Bot, New_session@1) ); {error, E@1} -> telega@log:error( <<"Failed to handle update:\n"/utf8, E@1/binary>> ), {stop, normal} end end; {bot_instanse_message_wait_handler, Handler@1} -> gleam@otp@actor:continue( erlang:setelement(7, Bot, {some, Handler@1}) ); bot_instanse_message_ok -> gleam@otp@actor:continue(Bot) end. -spec start_bot_instanse( registry(UDY), telega@update:update(), binary(), gleam@erlang@process:subject(gleam@erlang@process:subject(bot_instanse_message(UDY))) ) -> {ok, gleam@erlang@process:subject(bot_instanse_message(UDY))} | {error, gleam@otp@actor:start_error()}. start_bot_instanse(Registry, Update, Session_key, Parent_subject) -> gleam@otp@actor:start_spec( {spec, fun() -> Actor_subj = gleam@erlang@process:new_subject(), gleam@erlang@process:send(Parent_subject, Actor_subj), Selector = begin _pipe = gleam_erlang_ffi:new_selector(), gleam@erlang@process:selecting( _pipe, Actor_subj, fun gleam@function:identity/1 ) end, case get_session(erlang:element(4, Registry), Update) of {ok, Session} -> _pipe@1 = {bot_instanse, Session_key, Session, erlang:element(3, Registry), erlang:element(5, Registry), erlang:element(4, Registry), none, Actor_subj}, {ready, _pipe@1, Selector}; {error, E} -> {failed, <<"Failed to init bot instanse:\n"/utf8, E/binary>>} end end, 10000, fun handle_bot_instanse_message/2} ). -spec add_bot_instance(registry(UCY), binary(), telega@update:update()) -> gleam@otp@actor:next(any(), registry(UCY)). add_bot_instance(Registry, Session_key, Update) -> Registry_actor = gleam@otp@supervisor:supervisor( fun(_) -> start_bot_instanse( Registry, Update, Session_key, erlang:element(7, Registry) ) end ), _assert_subject = gleam@otp@supervisor:start( fun(_capture) -> gleam@otp@supervisor:add(_capture, Registry_actor) end ), {ok, _} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"telega/bot"/utf8>>, function => <<"add_bot_instance"/utf8>>, line => 96}) end, Bot_subject_result = begin _pipe = gleam@erlang@process:'receive'( erlang:element(7, Registry), 1000 ), gleam@result:map_error( _pipe, fun(E) -> <<"Failed to start bot instanse:\n"/utf8, (gleam@string:inspect(E))/binary>> end ) end, case Bot_subject_result of {ok, Bot_subject} -> case try_send_update(Bot_subject, Update) of {ok, _} -> gleam@otp@actor:continue( erlang:setelement( 2, Registry, gleam@dict:insert( erlang:element(2, Registry), Session_key, Bot_subject ) ) ); {error, E@1} -> telega@log:error( <<"Failed to send message to bot instanse: "/utf8, (gleam@string:inspect(E@1))/binary>> ), gleam@otp@actor:continue(Registry) end; {error, E@2} -> telega@log:error(E@2), gleam@otp@actor:continue(Registry) end. -spec handle_registry_message(registry_message(), registry(UCV)) -> gleam@otp@actor:next(any(), registry(UCV)). handle_registry_message(Message, Registry) -> case Message of {handle_bot_registry_message, Message@1} -> case get_session_key(Message@1) of {ok, Session_key} -> case gleam@dict:get( erlang:element(2, Registry), Session_key ) of {ok, Registry_item} -> case try_send_update(Registry_item, Message@1) of {ok, _} -> gleam@otp@actor:continue(Registry); {error, _} -> add_bot_instance( Registry, Session_key, Message@1 ) end; {error, nil} -> add_bot_instance(Registry, Session_key, Message@1) end; {error, E} -> telega@log:error( <<"Failed to get session key: "/utf8, (gleam@string:inspect(E))/binary>> ), gleam@otp@actor:continue(Registry) end end. -spec start_registry( telega@internal@config:config(), list(handler(UDD)), session_settings(UDD), gleam@erlang@process:subject(gleam@erlang@process:subject(registry_message())) ) -> {ok, gleam@erlang@process:subject(registry_message())} | {error, gleam@otp@actor:start_error()}. start_registry(Config, Handlers, Session_settings, Root_subject) -> gleam@otp@actor:start_spec( {spec, fun() -> Registry_subject = gleam@erlang@process:new_subject(), Bot_instances_subject = gleam@erlang@process:new_subject(), gleam@erlang@process:send(Root_subject, Registry_subject), Selector = begin _pipe = gleam_erlang_ffi:new_selector(), gleam@erlang@process:selecting( _pipe, Registry_subject, fun gleam@function:identity/1 ) end, _pipe@1 = {registry, gleam@dict:new(), Config, Session_settings, Handlers, Registry_subject, Bot_instances_subject}, {ready, _pipe@1, Selector} end, 10000, fun handle_registry_message/2} ).