-module(discord_gleam@ws@event_loop). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/discord_gleam/ws/event_loop.gleam"). -export([start_event_loop/5]). -export_type([event_loop_message/0, websocket_state/2, websocket_message/1]). -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( " Event loop for handling the discord gateway websocket\n" " Dispatches events to registered event handlers\n" ). -type event_loop_message() :: start | {restart, binary(), binary()} | stop. -type websocket_state(LMV, LMW) :: {state, boolean(), integer(), gleam@erlang@process:subject(event_loop_message()), LMV, discord_gleam@types@bot:bot(), discord_gleam@event_handler:mode(LMV, LMW)}. -type websocket_message(LMX) :: {bot_message, discord_gleam@types@bot:bot_message()} | {user, LMX}. -file("src/discord_gleam/ws/event_loop.gleam", 459). -spec on_close( websocket_state(any(), any()), booklet:booklet(gleam@dict:dict(binary(), binary())), stratus:close_reason() ) -> nil. on_close(State, State_ets, Close_reason) -> logging:log(debug, <<"The webhook was closed"/utf8>>), case Close_reason of {custom, Custom_close_reason} -> case stratus:get_custom_code(Custom_close_reason) of 4000 -> logging:log( error, <<"Unknown error, not reconnecting"/utf8>> ); 4001 -> logging:log( error, <<"Unknown opcode, not reconnecting"/utf8>> ); 4002 -> logging:log( error, <<"Decode error, open a github issue, not reconnecting"/utf8>> ); 4003 -> logging:log( error, <<"Not authenticated, not reconnecting"/utf8>> ); 4004 -> logging:log( error, <<"Authentication failed, check your token, not reconnecting"/utf8>> ); 4005 -> logging:log( error, <<"Already authenticated, open a github issue, not reconnecting"/utf8>> ); 4007 -> logging:log( error, <<"Invalid sequence, reconnecting"/utf8>> ), Host = case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"resume_gateway_url"/utf8>> ) of {ok, Url} -> Url; {error, _} -> <<"gateway.discord.gg"/utf8>> end, gleam@erlang@process:send( erlang:element(4, State), {restart, Host, <<""/utf8>>} ); 4008 -> logging:log( error, <<"You have been ratelimited, not reconnecting"/utf8>> ); 4009 -> logging:log( error, <<"Session timed out, reconnecting"/utf8>> ), Host@1 = case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"resume_gateway_url"/utf8>> ) of {ok, Url@1} -> Url@1; {error, _} -> <<"gateway.discord.gg"/utf8>> end, Session_id = case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"session_id"/utf8>> ) of {ok, S} -> S; {error, _} -> <<""/utf8>> end, gleam@erlang@process:send( erlang:element(4, State), {restart, Host@1, Session_id} ); 4010 -> logging:log( error, <<"Invalid shard, not reconnecting"/utf8>> ); 4011 -> logging:log( error, <<"Sharding required, not reconnecting"/utf8>> ); 4012 -> logging:log( error, <<"Invalid API version, open a github issue on the discord_gleam repository, not reconnecting"/utf8>> ); 4013 -> logging:log( error, <<"Invalid intents used, open a github issue on the discord_gleam repository, not reconnecting"/utf8>> ); 4014 -> logging:log( error, <<"Disallowed intents used, did you remember to enable any priveleged intents you used in the Discord Developer Portal (https://discord.dev)? Not reconnecting"/utf8>> ); _ -> logging:log( error, <<"Unknown close code, not reconnecting"/utf8>> ) end; _ -> nil end. -file("src/discord_gleam/ws/event_loop.gleam", 271). -spec handle_text_message( stratus:connection(), websocket_state(LNQ, LNR), binary(), discord_gleam@types@bot:bot(), discord_gleam@event_handler:mode(LNQ, LNR), boolean(), binary(), booklet:booklet(gleam@dict:dict(binary(), binary())) ) -> stratus:next(websocket_state(LNQ, LNR), websocket_message(LNR)). handle_text_message( Conn, State, Msg, Bot, Mode, Reconnect, Session_id, State_ets ) -> logging:log(debug, <<"Gateway text msg: "/utf8, Msg/binary>>), case erlang:element(2, State) of false -> Identify = case Reconnect of true -> discord_gleam@ws@packets@identify:create_resume_packet( erlang:element(2, Bot), erlang:element(4, Bot), Session_id, case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"sequence"/utf8>> ) of {ok, S} -> S; {error, _} -> <<"0"/utf8>> end ); false -> discord_gleam@ws@packets@identify:create_packet( erlang:element(2, Bot), erlang:element(4, Bot) ) end, _ = stratus:send_text_message(Conn, Identify), New_state = {state, true, 0, erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State)}, case discord_gleam@ws@packets@hello:string_to_data(Msg) of {ok, Data} -> proc_lib:spawn_link( fun() -> repeatedly_ffi:call( erlang:element(2, erlang:element(3, Data)), nil, fun(_, _) -> S@2 = case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"sequence"/utf8>> ) of {ok, S@1} -> case gleam_stdlib:parse_int(S@1) of {ok, I} -> I; {error, _} -> 0 end; {error, _} -> 0 end, Packet = begin _pipe = gleam@json:object( [{<<"op"/utf8>>, gleam@json:int(1)}, {<<"d"/utf8>>, case S@2 of 0 -> gleam@json:null(); _ -> gleam@json:int(S@2) end}] ), gleam@json:to_string(_pipe) end, logging:log( debug, <<"Sending heartbeat: "/utf8, Packet/binary>> ), _ = stratus:send_text_message(Conn, Packet), nil end ) end ), nil; {error, Err} -> logging:log( critical, <<"Failed to decode hello packet: "/utf8, (discord_gleam@internal@error:json_decode_error_to_string( Err ))/binary>> ), _ = stratus:close(Conn, {normal, <<>>}), logging:log( critical, <<"Closing websocket due to fatal error"/utf8>> ) end, stratus:continue(New_state); true -> Generic_packet = discord_gleam@ws@packets@generic:string_to_data( Msg ), case erlang:element(3, Generic_packet) of 0 -> nil; _ -> booklet_ffi:update( State_ets, fun(Cache) -> gleam@dict:insert( Cache, <<"sequence"/utf8>>, case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"sequence"/utf8>> ) of {ok, S@3} -> S@3; {error, _} -> <<"0"/utf8>> end ) end ), nil end, case erlang:element(4, Generic_packet) of 7 -> logging:log(debug, <<"Received a reconnect request"/utf8>>), case stratus:close_custom(Conn, 4009, <<>>) of {ok, _} -> logging:log(debug, <<"Closed websocket"/utf8>>); {error, _} -> logging:log( error, <<"Failed to close websocket"/utf8>> ) end, Host = case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"resume_gateway_url"/utf8>> ) of {ok, Url} -> Url; {error, _} -> <<"gateway.discord.gg"/utf8>> end, Session_id@1 = case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"session_id"/utf8>> ) of {ok, S@4} -> S@4; {error, _} -> <<""/utf8>> end, gleam@erlang@process:send( erlang:element(4, State), {restart, Host, Session_id@1} ); _ -> nil end, New_state@1 = {state, true, erlang:element(3, Generic_packet), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State)}, Next = discord_gleam@event_handler:handle_event( Bot, erlang:element(5, State), {internal_packet, Msg}, Mode, State_ets ), case Next of {continue, User_state, Opt} -> New_state@2 = {state, erlang:element(2, New_state@1), erlang:element(3, New_state@1), erlang:element(4, New_state@1), User_state, erlang:element(6, New_state@1), erlang:element(7, New_state@1)}, Next@1 = stratus:continue(New_state@2), case Opt of {some, User_selector} -> stratus:with_selector( Next@1, gleam_erlang_ffi:map_selector( User_selector, fun(Field@0) -> {user, Field@0} end ) ); none -> Next@1 end; stop -> logging:log( debug, <<"Stopping discord websocket connection"/utf8>> ), stratus:stop(); {stop_abnormal, Reason} -> logging:log( error, <<"Stopping discord websocket connection with abnormal reason: "/utf8, Reason/binary>> ), stratus:stop_abnormal(Reason) end end. -file("src/discord_gleam/ws/event_loop.gleam", 109). -spec start_discord_websocket( discord_gleam@event_handler:mode(any(), any()), gleam@erlang@process:subject(event_loop_message()), binary(), boolean(), binary(), booklet:booklet(gleam@dict:dict(binary(), binary())) ) -> {ok, nil} | {error, gleam@otp@actor:start_error()}. start_discord_websocket( Mode, Event_loop_subject, Host, Reconnect, Session_id, State_ets ) -> Req = begin _pipe = gleam@http@request:new(), _pipe@1 = gleam@http@request:set_host(_pipe, Host), _pipe@2 = gleam@http@request:set_scheme(_pipe@1, https), _pipe@3 = gleam@http@request:set_path( _pipe@2, <<"/?v=10&encoding=json"/utf8>> ), _pipe@4 = gleam@http@request:set_header( _pipe@3, <<"User-Agent"/utf8>>, <<"DiscordBot (https://github.com/cyteon/discord_gleam, 2.1.0)"/utf8>> ), _pipe@5 = gleam@http@request:set_header( _pipe@4, <<"Host"/utf8>>, <<"gateway.discord.gg"/utf8>> ), _pipe@6 = gleam@http@request:set_header( _pipe@5, <<"Connection"/utf8>>, <<"Upgrade"/utf8>> ), _pipe@7 = gleam@http@request:set_header( _pipe@6, <<"Upgrade"/utf8>>, <<"websocket"/utf8>> ), gleam@http@request:set_header( _pipe@7, <<"Sec-WebSocket-Version"/utf8>>, <<"13"/utf8>> ) end, logging:log(debug, <<"Creating websocket client builder"/utf8>>), Started = begin _pipe@14 = stratus:new_with_initialiser( Req, fun() -> gleam@result:'try'( case discord_gleam@event_handler:name_from_mode(Mode) of {ok, Name} -> _pipe@8 = gleam_erlang_ffi:register_process( erlang:self(), Name ), _pipe@10 = gleam@result:map( _pipe@8, fun(_) -> _pipe@9 = gleam_erlang_ffi:new_selector(), gleam@erlang@process:select_map( _pipe@9, gleam@erlang@process:named_subject(Name), fun(Field@0) -> {user, Field@0} end ) end ), gleam@result:replace_error( _pipe@10, <<"failed to register name for websocket client process"/utf8>> ); {error, _} -> {ok, gleam_erlang_ffi:new_selector()} end, fun(Selector) -> Bot_message_subject = gleam@erlang@process:new_subject(), Bot = begin _record = discord_gleam@event_handler:bot_from_mode( Mode ), {bot, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), Bot_message_subject} end, Mode@1 = discord_gleam@event_handler:set_bot(Mode, Bot), Selector@1 = gleam@erlang@process:select_map( Selector, Bot_message_subject, fun(Field@0) -> {bot_message, Field@0} end ), {User_state@1, Selector@3} = case Mode@1 of {normal, _, _, On_init, _} -> {User_state, User_selector} = On_init( gleam_erlang_ffi:new_selector() ), Selector@2 = begin _pipe@11 = gleam_erlang_ffi:map_selector( User_selector, fun(Field@0) -> {user, Field@0} end ), gleam_erlang_ffi:merge_selector( Selector@1, _pipe@11 ) end, {User_state, Selector@2}; {simple, _, _, _, Nil_state} -> {Nil_state, Selector@1} end, Initial_state = {state, false, 0, Event_loop_subject, User_state@1, Bot, Mode@1}, _pipe@12 = stratus:initialised(Initial_state), _pipe@13 = stratus:selecting(_pipe@12, Selector@3), {ok, _pipe@13} end ) end ), _pipe@15 = stratus:on_message( _pipe@14, fun(State, Msg, Conn) -> case Msg of {text, Msg@1} -> handle_text_message( Conn, State, Msg@1, erlang:element(6, State), erlang:element(7, State), Reconnect, Session_id, State_ets ); {user, {bot_message, {send_packet, Packet}}} -> logging:log( debug, <<"User packet: "/utf8, Packet/binary>> ), _ = stratus:send_text_message(Conn, Packet), stratus:continue(State); {user, {user, Msg@2}} -> Next = discord_gleam@event_handler:handle_event( erlang:element(6, State), erlang:element(5, State), {internal_user, Msg@2}, erlang:element(7, State), State_ets ), case Next of {continue, User_state@2, Opt} -> New_state = {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), User_state@2, erlang:element(6, State), erlang:element(7, State)}, Next@1 = stratus:continue(New_state), case Opt of {some, User_selector@1} -> stratus:with_selector( Next@1, gleam_erlang_ffi:map_selector( User_selector@1, fun(Field@0) -> {user, Field@0} end ) ); none -> Next@1 end; stop -> logging:log( debug, <<"Stopping discord websocket connection"/utf8>> ), gleam@erlang@process:send( erlang:element(4, State), stop ), stratus:stop(); {stop_abnormal, Reason} -> logging:log( error, <<"Stopping discord websocket connection with abnormal reason: "/utf8, Reason/binary>> ), stratus:stop_abnormal(Reason) end; {binary, _} -> logging:log(debug, <<"Binary message"/utf8>>), stratus:continue(State) end end ), _pipe@16 = stratus:on_close( _pipe@15, fun(State@1, Close_reason) -> on_close(State@1, State_ets, Close_reason) end ), stratus:start(_pipe@16) end, case Started of {error, Err} -> {error, Err}; {ok, _} -> {ok, nil} end. -file("src/discord_gleam/ws/event_loop.gleam", 32). ?DOC(" Start the event loop, with a set of event handlers.\n"). -spec start_event_loop( discord_gleam@event_handler:mode(any(), any()), binary(), boolean(), binary(), booklet:booklet(gleam@dict:dict(binary(), binary())) ) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(event_loop_message()))} | {error, gleam@otp@actor:start_error()}. start_event_loop(Mode, Host, Reconnect, Session_id, State_ets) -> logging:log(debug, <<"Starting event loop"/utf8>>), _pipe@2 = gleam@otp@actor:new_with_initialiser( 1000, fun(Subject) -> logging:log(debug, <<"Sending start message"/utf8>>), gleam@otp@actor:send(Subject, start), _pipe = gleam@otp@actor:initialised(Subject), _pipe@1 = gleam@otp@actor:returning(_pipe, Subject), {ok, _pipe@1} end ), _pipe@3 = gleam@otp@actor:on_message( _pipe@2, fun(Subject@1, Msg) -> case Msg of start -> logging:log(debug, <<"Received start message"/utf8>>), Started = start_discord_websocket( Mode, Subject@1, Host, Reconnect, Session_id, State_ets ), case Started of {ok, nil} -> gleam@otp@actor:continue(Subject@1); {error, _} -> gleam@otp@actor:stop_abnormal( <<"failed to start discord websocket"/utf8>> ) end; {restart, Host@1, Session_id@1} -> logging:log(debug, <<"Restarting discord websocket"/utf8>>), Started@1 = start_discord_websocket( Mode, Subject@1, Host@1, Reconnect, Session_id@1, State_ets ), case Started@1 of {ok, nil} -> gleam@otp@actor:continue(Subject@1); {error, _} -> gleam@otp@actor:stop_abnormal( <<"failed to restart discord websocket"/utf8>> ) end; stop -> gleam@otp@actor:stop() end end ), gleam@otp@actor:start(_pipe@3).