-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([main/6]). -export_type([state/0]). -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 state() :: {state, boolean(), integer()}. -file("src/discord_gleam/ws/event_loop.gleam", 30). ?DOC(" Start the event loop, with a set of event handlers.\n"). -spec main( discord_gleam@types@bot:bot(), list(fun((discord_gleam@types@bot:bot(), discord_gleam@event_handler:packet()) -> nil)), binary(), boolean(), binary(), booklet:booklet(gleam@dict:dict(binary(), binary())) ) -> nil. main(Bot, Event_handlers, Host, Reconnect, Session_id, State_ets) -> logging:log(debug, <<"Requesting gateway"/utf8>>), booklet_ffi:update( State_ets, fun(Cache) -> gleam@dict:insert(Cache, <<"sequence"/utf8>>, <<"0"/utf8>>) end ), Host@1 = gleam@string:replace(Host, <<"wss://"/utf8>>, <<""/utf8>>), Req = begin _pipe = gleam@http@request:new(), _pipe@1 = gleam@http@request:set_host(_pipe, Host@1), _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, 1.7.1)"/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 builder"/utf8>>), Initial_state = {state, false, 0}, Name = gleam_erlang_ffi:new_name(<<"user_msg_subject"/utf8>>), Bot@1 = {bot, erlang:element(2, Bot), erlang:element(3, Bot), erlang:element(4, Bot), erlang:element(5, Bot), {some, Name}}, Builder = begin _pipe@12 = stratus:new_with_initialiser( Req, fun() -> _pipe@8 = gleam_erlang_ffi:register_process(erlang:self(), Name), _pipe@11 = gleam@result:map( _pipe@8, fun(_) -> Selector = begin _pipe@9 = gleam_erlang_ffi:new_selector(), gleam@erlang@process:select( _pipe@9, gleam@erlang@process:named_subject(Name) ) end, _pipe@10 = stratus:initialised(Initial_state), stratus:selecting(_pipe@10, Selector) end ), gleam@result:replace_error( _pipe@11, <<"Failed to initialise websocket client"/utf8>> ) end ), _pipe@14 = stratus:on_message( _pipe@12, fun(State, Msg, Conn) -> case Msg of {text, Msg@1} -> logging:log( debug, <<"Gateway text msg: "/utf8, Msg@1/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@1), erlang:element(4, Bot@1), 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@1), erlang:element(4, Bot@1) ) end, _ = stratus:send_text_message(Conn, Identify), New_state = {state, true, 0}, case discord_gleam@ws@packets@hello:string_to_data( Msg@1 ) 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@13 = 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@13 ) 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@1 ), case erlang:element(3, Generic_packet) of 0 -> nil; _ -> booklet_ffi:update( State_ets, fun(Cache@1) -> gleam@dict:insert( Cache@1, <<"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, main( Bot@1, Event_handlers, case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"resume_gateway_url"/utf8>> ) of {ok, Url} -> Url; {error, _} -> <<"gateway.discord.gg"/utf8>> end, Reconnect, case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"session_id"/utf8>> ) of {ok, S@4} -> S@4; {error, _} -> <<""/utf8>> end, State_ets ); _ -> nil end, New_state@1 = {state, true, erlang:element(3, Generic_packet)}, discord_gleam@event_handler:handle_event( Bot@1, Msg@1, Event_handlers, State_ets ), stratus:continue(New_state@1) end; {user, {send_packet, Packet@1}} -> logging:log( debug, <<"User packet: "/utf8, Packet@1/binary>> ), _ = stratus:send_text_message(Conn, Packet@1), stratus:continue(State); {binary, _} -> logging:log(debug, <<"Binary message"/utf8>>), stratus:continue(State) end end ), stratus:on_close( _pipe@14, fun(_, 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>> ), main( Bot@1, Event_handlers, 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, Reconnect, <<""/utf8>>, State_ets ); 4008 -> logging:log( error, <<"You have been ratelimited, not reconnecting"/utf8>> ); 4009 -> logging:log( error, <<"Session timed out, reconnecting"/utf8>> ), main( Bot@1, Event_handlers, case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"resume_gateway_url"/utf8>> ) of {ok, Url@2} -> Url@2; {error, _} -> <<"gateway.discord.gg"/utf8>> end, Reconnect, case gleam_stdlib:map_get( booklet_ffi:get(State_ets), <<"session_id"/utf8>> ) of {ok, S@5} -> S@5; {error, _} -> <<""/utf8>> end, State_ets ); 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; _ -> nil end, nil end ) end, Actor@1 = case stratus:start(Builder) of {ok, Actor} -> Actor; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"discord_gleam/ws/event_loop"/utf8>>, function => <<"main"/utf8>>, line => 365, value => _assert_fail, start => 10903, 'end' => 10948, pattern_start => 10914, pattern_end => 10923}) end, Pid@1 = case gleam@erlang@process:subject_owner(erlang:element(3, Actor@1)) of {ok, Pid} -> Pid; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"discord_gleam/ws/event_loop"/utf8>>, function => <<"main"/utf8>>, line => 366, value => _assert_fail@1, start => 10951, 'end' => 11005, pattern_start => 10962, pattern_end => 10969}) end, Monitor = gleam@erlang@process:monitor(Pid@1), Selector@1 = gleam_erlang_ffi:new_selector(), Selector@2 = gleam@erlang@process:select_specific_monitor( Selector@1, Monitor, fun gleam@function:identity/1 ), _ = gleam_erlang_ffi:select(Selector@2), logging:log(error, <<"Event loop has exited, bye bye"/utf8>>), gleam_erlang_ffi:sleep(1000).