-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(), boolean()} | stop. -type websocket_state(OHU, OHV) :: {state, boolean(), gleam@erlang@process:subject(event_loop_message()), OHU, discord_gleam@bot:bot(), discord_gleam@event_handler:mode(OHU, OHV), gleam@option:option(repeatedly:repeater(nil))}. -type websocket_message(OHW) :: {bot_message, discord_gleam@bot:bot_message()} | {user, OHW}. -file("src/discord_gleam/ws/event_loop.gleam", 485). -spec on_close( websocket_state(any(), any()), booklet:booklet(discord_gleam@ws@gateway_state:gateway_state()), stratus:close_reason() ) -> nil. on_close(State, State_ets, Close_reason) -> logging:log(debug, <<"The websocket was closed"/utf8>>), _ = gleam@option:map(erlang:element(7, State), fun repeatedly_ffi:stop/1), case Close_reason of {custom, Custom_close_reason} -> case stratus:get_custom_code(Custom_close_reason) of 4000 -> logging:log(error, <<"Unknown error, reconnecting"/utf8>>), Host = erlang:element(4, booklet_ffi:get(State_ets)), Session_id = erlang:element(3, booklet_ffi:get(State_ets)), gleam@erlang@process:send( erlang:element(3, State), {restart, Host, Session_id, true} ); 4001 -> logging:log( error, <<"Unknown opcode, not reconnecting"/utf8>> ); 4002 -> logging:log(error, <<"Decode error, reconnecting"/utf8>>), Host@1 = erlang:element(4, booklet_ffi:get(State_ets)), Session_id@1 = erlang:element(3, booklet_ffi:get(State_ets)), gleam@erlang@process:send( erlang:element(3, State), {restart, Host@1, Session_id@1, true} ); 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@2 = erlang:element(4, booklet_ffi:get(State_ets)), gleam@erlang@process:send( erlang:element(3, State), {restart, Host@2, <<""/utf8>>, false} ); 4008 -> logging:log( error, <<"You have been ratelimited, not reconnecting"/utf8>> ); 4009 -> logging:log( error, <<"Session timed out, reconnecting"/utf8>> ), Host@3 = erlang:element(4, booklet_ffi:get(State_ets)), Session_id@2 = erlang:element(3, booklet_ffi:get(State_ets)), gleam@erlang@process:send( erlang:element(3, State), {restart, Host@3, Session_id@2, true} ); 4010 -> logging:log( error, <<"Invalid shard, not reconnecting"/utf8>> ), logging:log( error, <<"discord_gleam does currently not support sharding"/utf8>> ); 4011 -> logging:log( error, <<"Sharding required, not reconnecting."/utf8>> ), logging:log( error, <<"discord_gleam does currently not support sharding"/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 privileged intents you used in the Discord Developer Portal (https://discord.dev)? Not reconnecting"/utf8>> ); _ -> logging:log( error, <<"Unknown close code, not reconnecting"/utf8>> ) end; _ -> Host@4 = erlang:element(4, booklet_ffi:get(State_ets)), Session_id@3 = erlang:element(3, booklet_ffi:get(State_ets)), logging:log(debug, <<"Reconnecting to the gateway"/utf8>>), gleam@erlang@process:send( erlang:element(3, State), {restart, Host@4, Session_id@3, true} ) end. -file("src/discord_gleam/ws/event_loop.gleam", 279). -spec handle_text_message( stratus:connection(), websocket_state(OIL, OIM), binary(), discord_gleam@bot:bot(), discord_gleam@event_handler:mode(OIL, OIM), boolean(), binary(), booklet:booklet(discord_gleam@ws@gateway_state:gateway_state()) ) -> stratus:next(websocket_state(OIL, OIM), websocket_message(OIM)). 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 -> Generic = discord_gleam@ws@packets@generic:from_json_string(Msg), case erlang:element(4, Generic) of 10 -> case discord_gleam@ws@packets@hello:from_json_string(Msg) of {ok, Data} -> Repeater = repeatedly_ffi:call( erlang:element(2, erlang:element(3, Data)), nil, fun(_, _) -> S = erlang:element( 2, booklet_ffi:get(State_ets) ), Packet = begin _pipe = gleam@json:object( [{<<"op"/utf8>>, gleam@json:int(1)}, {<<"d"/utf8>>, case S of 0 -> gleam@json:null(); _ -> gleam@json:int(S) end}] ), gleam@json:to_string(_pipe) end, logging:log( debug, <<"Sending heartbeat: "/utf8, Packet/binary>> ), _ = stratus:send_text_message(Conn, Packet), nil end ), Identify = case Reconnect of true -> discord_gleam@ws@packets@identify:create_resume_packet( erlang:element(2, Bot), Session_id, erlang:element( 2, booklet_ffi:get(State_ets) ) ); false -> discord_gleam@ws@packets@identify:create_packet( erlang:element(2, Bot), erlang:element(4, Bot) ) end, _ = gleam@option:map( erlang:element(7, State), fun repeatedly_ffi:stop/1 ), _ = stratus:send_text_message(Conn, Identify), New_state = {state, true, erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), {some, Repeater}}, stratus:continue(New_state); {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>> ), stratus:continue(State) end; _ -> stratus:continue(State) end; true -> Generic_packet = discord_gleam@ws@packets@generic:from_json_string( Msg ), case erlang:element(3, Generic_packet) of {some, S@1} -> booklet_ffi:update( State_ets, fun(State@1) -> {gateway_state, S@1, erlang:element(3, State@1), erlang:element(4, State@1)} end ), nil; _ -> 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 = erlang:element(4, booklet_ffi:get(State_ets)), Session_id@1 = erlang:element(3, booklet_ffi:get(State_ets)), gleam@erlang@process:send( erlang:element(3, State), {restart, Host, Session_id@1, true} ); 9 -> logging:log(debug, <<"Invalid session, reconnecting"/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@1 = erlang:element(4, booklet_ffi:get(State_ets)), Session_id@2 = erlang:element(3, booklet_ffi:get(State_ets)), Decoder = begin gleam@dynamic@decode:field( <<"d"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(D) -> gleam@dynamic@decode:success(D) end ) end, Decoded = case gleam@json:parse(Msg, Decoder) of {ok, D@1} -> D@1; {error, _} -> false end, gleam@erlang@process:send( erlang:element(3, State), {restart, Host@1, Session_id@2, Decoded} ); _ -> nil end, New_state@1 = {state, true, erlang:element(3, State), 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(4, 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), User_state, erlang:element(5, New_state@1), 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", 116). -spec start_discord_websocket( discord_gleam@event_handler:mode(any(), any()), gleam@erlang@process:subject(event_loop_message()), binary(), boolean(), binary(), booklet:booklet(discord_gleam@ws@gateway_state:gateway_state()) ) -> {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, 3.0.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), erlang:element(6, _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, Event_loop_subject, User_state@1, Bot, Mode@1, none}, _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(5, State), erlang:element(6, 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(5, State), erlang:element(4, State), {internal_user, Msg@2}, erlang:element(6, State), State_ets ), case Next of {continue, User_state@2, Opt} -> New_state = {state, erlang:element(2, State), erlang:element(3, State), User_state@2, erlang:element(5, State), 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(3, 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(discord_gleam@ws@gateway_state:gateway_state()) ) -> {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, Reconnect@1} -> logging:log(debug, <<"Restarting discord websocket"/utf8>>), gleam_erlang_ffi:sleep(2000), Started@1 = start_discord_websocket( Mode, Subject@1, Host@1, Reconnect@1, 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).