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(), 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).