Current section
Files
Jump to
Current section
Files
src/discord_gleam@ws@event_loop.erl
-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(UDY, UDZ) :: {state,
boolean(),
integer(),
gleam@erlang@process:subject(event_loop_message()),
UDY,
discord_gleam@types@bot:bot(),
discord_gleam@event_handler:mode(UDY, UDZ)}.
-type websocket_message(UEA) :: {bot_message,
discord_gleam@types@bot:bot_message()} |
{user, UEA}.
-file("src/discord_gleam/ws/event_loop.gleam", 275).
-spec handle_text_message(
stratus:connection(),
websocket_state(UET, UEU),
binary(),
discord_gleam@types@bot:bot(),
discord_gleam@event_handler:mode(UET, UEU),
boolean(),
binary(),
booklet:booklet(gleam@dict:dict(binary(), binary()))
) -> stratus:next(websocket_state(UET, UEU), websocket_message(UEU)).
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", 463).
-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", 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, {actor_failed, Actor_failed}} ->
{error, Actor_failed};
{error, {handshake_failed, _}} ->
{error, {init_failed, <<"handshake failed"/utf8>>}};
{error, {failed_to_transfer_socket, _}} ->
{error, {init_failed, <<"failed to transfer socket"/utf8>>}};
{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).