mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat(gateway): list bot ready guilds as unavailable (#2774)
This commit is contained in:
@@ -146,7 +146,7 @@ The initial session state. Sent once after a successful [Identify](/gateway/comm
|
||||
| _timings? | object | HTTP-side timing breakdown, present only for a staff account |
|
||||
| _timings_gw? | object | Gateway-side timing breakdown, present only for a staff account |
|
||||
|
||||
<sup>1</sup> A bot session always receives an empty array here and the guilds arrive as the [Guild Create](#guild-create) burst described below
|
||||
<sup>1</sup> On a bot session every entry is an unavailable guild with `id` and `unavailable: true` alone. The burst described below then sends a [Guild Create](#guild-create) with the full state of each available guild, and a [Guild Delete](#guild-delete) for each unavailable one
|
||||
|
||||
<sup>2</sup> Each entry has its `user` field removed and the removed accounts appear in `users` instead, so a client resolves a relationship through the entry's `id`
|
||||
|
||||
@@ -156,7 +156,7 @@ The initial session state. Sent once after a successful [Identify](/gateway/comm
|
||||
|
||||
Ready is sent outside the replay buffer, so a [Resume](/gateway/commands/#resume) never replays it.
|
||||
|
||||
A bot session receives one [Guild Create](#guild-create) per available guild immediately after Ready, and one [Guild Delete](#guild-delete) per unavailable guild. Those Dispatches are also sent outside the replay buffer.
|
||||
A bot session receives one [Guild Create](#guild-create) per available guild immediately after Ready, and one [Guild Delete](#guild-delete) per unavailable guild. Together they resolve the entries of the `guilds` array. Those Dispatches are also sent outside the replay buffer.
|
||||
|
||||
Shortly after Ready, every session receives one [Call Create](#call-create) for each of its private channels that has an active call.
|
||||
|
||||
@@ -187,9 +187,9 @@ The same structure appears in Ready, [Guild Create](#guild-create), and [Guild S
|
||||
|
||||
<sup>3</sup> Guild presences arrive as separate [Presence Update](#presence-update) and [Presence Update Bulk](#presence-update-bulk) Dispatches
|
||||
|
||||
An unavailable guild is reduced to `id` and `unavailable: true`, plus `unavailable_hidden: true` when the guild is hidden. It has none of the other fields.
|
||||
An unavailable guild is reduced to `id` and `unavailable: true`, plus `unavailable_hidden: true` when the guild is hidden. It has none of the other fields. Every entry in a bot session's [Ready](#ready) `guilds` array has this form, with `id` and `unavailable: true` alone.
|
||||
|
||||
Inside [Ready](#ready), and inside the [Guild Create](#guild-create) burst a bot receives immediately after Ready, each entry in `members` has its `user` replaced by `{"id": "..."}`. On a user session the removed accounts appear in the Ready payload's `users` array. A bot's `users` array is empty, so a bot pulls those accounts with [Request Guild Members](/gateway/commands/#request-guild-members). A [Guild Create](#guild-create) sent later in the session, and every [Guild Sync](#guild-sync), have the members with `user` intact.
|
||||
Inside [Ready](#ready), and inside the [Guild Create](#guild-create) burst a bot receives immediately after Ready, each entry in `members` has its `user` replaced by `{"id": "..."}`. A bot session's Ready has no `members` at all, because each of its guilds is an unavailable guild, so a bot sees the reduced form in the burst alone. On a user session the removed accounts appear in the Ready payload's `users` array. A bot's `users` array is empty, so a bot pulls those accounts with [Request Guild Members](/gateway/commands/#request-guild-members). A [Guild Create](#guild-create) sent later in the session, and every [Guild Sync](#guild-sync), have the members with `user` intact.
|
||||
|
||||
#### Session presence object
|
||||
|
||||
@@ -380,7 +380,9 @@ A guild became available to the session. The payload is a [guild ready object](#
|
||||
|
||||
`roles`, `channels`, `emojis`, `stickers`, and `voice_states` are always complete. A client that stores any of them for the guild replaces its stored list with the new array. `members` is a partial list. A client adds or updates those members and keeps every other member it already stores.
|
||||
|
||||
A user session receives Guild Create when a guild becomes available after Ready, for example after joining one or after an unavailable guild recovers. A bot session receives one for every guild in the burst that follows Ready.
|
||||
Every session receives Guild Create when a guild becomes available after Ready, for example after joining one or after an unavailable guild recovers. A bot session also receives one for each available guild in the burst that follows Ready.
|
||||
|
||||
A bot session's Guild Create has `unavailable: false` unless it is the first Guild Create for a guild the bot joined during the session and [Ready](#ready) did not list. That Dispatch has no `unavailable` field, even when a [Guild Delete](#guild-delete) with `unavailable: true` for the guild came before it. A user session never receives the field on Guild Create.
|
||||
|
||||
### <span id="guild-sync"></span>GUILD_SYNC
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ A guild belongs to `((guild_id >> 22) % shard_count)`, computed on the integer v
|
||||
|
||||
The pair selects the session's guild membership. At Identify, Fluxer filters the account's guild list to the guilds the shard owns, and the session connects only to those.
|
||||
|
||||
For a user session, the filtered set is also the [Ready](/gateway/events/#ready) `guilds` array. For a bot session, it is the guild burst of [Guild Create](/gateway/events/#guild-create) and [Guild Delete](/gateway/events/#guild-delete) Dispatches after Ready. Ready echoes the accepted pair back as `shard`.
|
||||
On every session, the filtered set is also the [Ready](/gateway/events/#ready) `guilds` array. A bot session has each of those guilds as an unavailable guild, and one [Guild Create](/gateway/events/#guild-create) or [Guild Delete](/gateway/events/#guild-delete) per guild follows Ready. Ready echoes the accepted pair back as `shard`.
|
||||
|
||||
Fluxer checks only a bot session against the guild ceiling. A bot whose shard owns more than 2,500 guilds closes with `4011` and reason `Sharding required`. A bot that supplies no pair is checked against its whole guild list. A user session is bounded by the 100-session-per-user limit alone, whatever its guild count.
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
guild_connect_inflight => #{guild_id() => non_neg_integer()},
|
||||
guild_connect_workers => #{reference() => {guild_id(), non_neg_integer(), pid()}},
|
||||
guild_connect_timers => #{guild_id() => {reference(), reference()}},
|
||||
pending_guild_joins => #{guild_id() => true},
|
||||
voice_queue => queue:queue(map()),
|
||||
voice_queue_timer => reference() | undefined,
|
||||
debounce_reactions => boolean(),
|
||||
@@ -168,7 +169,7 @@ handle_cast_guild_or_lifecycle({reconnect_drain, SocketPid}, State) when
|
||||
session_lifecycle:handle_reconnect_drain(SocketPid, State);
|
||||
handle_cast_guild_or_lifecycle({guild_join, GuildId}, State) when is_integer(GuildId) ->
|
||||
self() ! {guild_connect, GuildId, 0},
|
||||
{noreply, State};
|
||||
{noreply, session_bot_guilds:track_join(GuildId, State)};
|
||||
handle_cast_guild_or_lifecycle({store_guild_subscriptions, Data}, State) when is_map(Data) ->
|
||||
{noreply, session_guilds:store_guild_subscriptions(Data, State)};
|
||||
handle_cast_guild_or_lifecycle({guild_leave, GuildId, forced_unavailable, true}, State) when
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
-module(session_bot_guilds).
|
||||
-typing([eqwalizer]).
|
||||
|
||||
-export([
|
||||
guild_event/3,
|
||||
track_join/2,
|
||||
forget_joins/2
|
||||
]).
|
||||
|
||||
-export_type([session_state/0, guild_id/0, event/0]).
|
||||
|
||||
-type session_state() :: session:session_state().
|
||||
-type guild_id() :: session:guild_id().
|
||||
-type event() :: session_dispatch:event().
|
||||
|
||||
-spec guild_event(event(), map(), session_state()) -> {map(), session_state()}.
|
||||
guild_event(guild_create, Data, #{bot := true} = State) ->
|
||||
guild_create(type_conv:extract_id(Data, <<"id">>), Data, State);
|
||||
guild_event(guild_delete, #{<<"unavailable">> := true} = Data, State) ->
|
||||
{Data, State};
|
||||
guild_event(guild_delete, Data, #{bot := true} = State) ->
|
||||
{Data, forget_join(type_conv:extract_id(Data, <<"id">>), State)};
|
||||
guild_event(_Event, Data, State) ->
|
||||
{Data, State}.
|
||||
|
||||
-spec guild_create(guild_id() | undefined, map(), session_state()) -> {map(), session_state()}.
|
||||
guild_create(undefined, Data, State) ->
|
||||
{Data#{<<"unavailable">> => false}, State};
|
||||
guild_create(GuildId, Data, State) ->
|
||||
Pending = pending_joins(State),
|
||||
case maps:is_key(GuildId, Pending) of
|
||||
true -> {Data, State#{pending_guild_joins => maps:remove(GuildId, Pending)}};
|
||||
false -> {Data#{<<"unavailable">> => false}, State}
|
||||
end.
|
||||
|
||||
-spec track_join(guild_id(), session_state()) -> session_state().
|
||||
track_join(GuildId, #{bot := true} = State) ->
|
||||
State#{pending_guild_joins => (pending_joins(State))#{GuildId => true}};
|
||||
track_join(_GuildId, State) ->
|
||||
State.
|
||||
|
||||
-spec forget_join(guild_id() | undefined, session_state()) -> session_state().
|
||||
forget_join(undefined, State) ->
|
||||
State;
|
||||
forget_join(GuildId, State) ->
|
||||
forget_joins([GuildId], State).
|
||||
|
||||
-spec forget_joins([guild_id()], session_state()) -> session_state().
|
||||
forget_joins(GuildIds, State) ->
|
||||
State#{pending_guild_joins => maps:without(GuildIds, pending_joins(State))}.
|
||||
|
||||
-spec pending_joins(session_state()) -> #{guild_id() => true}.
|
||||
pending_joins(State) ->
|
||||
maps:get(pending_guild_joins, State, #{}).
|
||||
@@ -33,9 +33,17 @@ handle_dispatch(Event, Data, State) ->
|
||||
should_skip_for_shard(Event, Data, State) orelse should_ignore_event(Event, Data, State)
|
||||
of
|
||||
true -> {noreply, State};
|
||||
false -> route_dispatch(Event, Data, State)
|
||||
false -> route_bot_guild_event(Event, Data, State)
|
||||
end.
|
||||
|
||||
-spec route_bot_guild_event(event(), map() | list(), session_state()) ->
|
||||
{noreply, session_state()}.
|
||||
route_bot_guild_event(Event, Data, State) when is_map(Data) ->
|
||||
{BotData, BotState} = session_bot_guilds:guild_event(Event, Data, State),
|
||||
route_dispatch(Event, BotData, BotState);
|
||||
route_bot_guild_event(Event, Data, State) ->
|
||||
route_dispatch(Event, Data, State).
|
||||
|
||||
-spec should_skip_for_shard(
|
||||
event(), map() | list() | {pre_encoded, binary()}, session_state()
|
||||
) ->
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
build_ignored_events_map/1,
|
||||
load_private_channels/1,
|
||||
load_relationships/1,
|
||||
ensure_bot_ready_map/1,
|
||||
build_state/1,
|
||||
schedule_timers/1
|
||||
]).
|
||||
@@ -153,11 +152,6 @@ load_relationships(Ready) when is_map(Ready) ->
|
||||
load_relationships(_) ->
|
||||
#{}.
|
||||
|
||||
-spec ensure_bot_ready_map(term()) -> map().
|
||||
ensure_bot_ready_map(undefined) -> #{<<"guilds">> => []};
|
||||
ensure_bot_ready_map(Ready) when is_map(Ready) -> Ready#{<<"guilds">> => []};
|
||||
ensure_bot_ready_map(_) -> #{<<"guilds">> => []}.
|
||||
|
||||
-spec build_state(map()) -> session_state().
|
||||
build_state(SessionData) ->
|
||||
BuildStartedAt = gateway_timings:start(),
|
||||
@@ -307,7 +301,8 @@ extract_is_staff(_) ->
|
||||
false.
|
||||
|
||||
-spec init_ready(boolean(), map() | undefined) -> map() | undefined.
|
||||
init_ready(true, Ready) -> ensure_bot_ready_map(Ready);
|
||||
init_ready(true, Ready) when is_map(Ready) -> Ready;
|
||||
init_ready(true, _Ready) -> undefined;
|
||||
init_ready(false, Ready) -> Ready.
|
||||
|
||||
-spec init_ack_seq(seq(), seq()) -> seq().
|
||||
@@ -502,14 +497,16 @@ base_session_data(Ready) ->
|
||||
ready => Ready
|
||||
}.
|
||||
|
||||
ensure_bot_ready_map_test() ->
|
||||
?assertEqual(#{<<"guilds">> => []}, ensure_bot_ready_map(undefined)),
|
||||
?assertEqual(
|
||||
#{<<"guilds">> => [], <<"user">> => #{}}, ensure_bot_ready_map(#{<<"user">> => #{}})
|
||||
),
|
||||
?assertEqual(#{<<"guilds">> => []}, ensure_bot_ready_map(not_a_map)),
|
||||
init_ready_keeps_transferred_bot_ready_undefined_test() ->
|
||||
?assertEqual(undefined, init_ready(true, undefined)),
|
||||
?assertEqual(#{<<"user">> => #{}}, init_ready(true, #{<<"user">> => #{}})),
|
||||
?assertEqual(undefined, init_ready(false, undefined)),
|
||||
ok.
|
||||
|
||||
build_state_keeps_transferred_bot_ready_undefined_test() ->
|
||||
Data = (base_session_data(undefined))#{bot => true},
|
||||
?assertEqual(undefined, maps:get(ready, build_state(Data))).
|
||||
|
||||
normalize_guild_ids_filters_invalid_values_test() ->
|
||||
?assertEqual([1, 2], normalize_guild_ids([1, 0, -1, 2, <<"3">>])),
|
||||
?assertEqual([10], normalize_guild_ids(#{10 => undefined, <<"bad">> => undefined})),
|
||||
|
||||
@@ -173,9 +173,8 @@ build_final_ready_data(
|
||||
SessionId,
|
||||
IsBot
|
||||
) ->
|
||||
ReadyData = prepare_ready_base(State, IsBot),
|
||||
AllGuildStates = build_all_guild_states(CollectedGuilds, Guilds),
|
||||
GuildsForReady = guilds_for_ready_payload(IsBot, AllGuildStates),
|
||||
ReadyData = prepare_ready_base(State),
|
||||
GuildsForReady = guilds_for_ready_payload(IsBot, CollectedGuilds, Guilds),
|
||||
FinalReadyData = ReadyData#{
|
||||
<<"guilds">> => GuildsForReady,
|
||||
<<"sessions">> => CollectedSessions,
|
||||
@@ -186,15 +185,11 @@ build_final_ready_data(
|
||||
},
|
||||
gateway_sharding:maybe_put_ready_shard(FinalReadyData, maps:get(shard, State, undefined)).
|
||||
|
||||
-spec prepare_ready_base(session_state(), boolean()) -> map().
|
||||
prepare_ready_base(#{ready := undefined}, _IsBot) ->
|
||||
-spec prepare_ready_base(session_state()) -> map().
|
||||
prepare_ready_base(#{ready := undefined}) ->
|
||||
#{<<"guilds">> => []};
|
||||
prepare_ready_base(#{ready := Ready}, IsBot) ->
|
||||
Stripped = session_ready_collect:strip_user_from_relationships(Ready),
|
||||
case IsBot of
|
||||
true -> Stripped#{<<"guilds">> => []};
|
||||
false -> Stripped
|
||||
end.
|
||||
prepare_ready_base(#{ready := Ready}) ->
|
||||
session_ready_collect:strip_user_from_relationships(Ready).
|
||||
|
||||
-spec build_all_guild_states([map()], map()) -> [map()].
|
||||
build_all_guild_states(CollectedGuilds, Guilds) ->
|
||||
@@ -225,9 +220,13 @@ collect_unavailable_placeholder(GuildId, _Value, StrippedGuildIds, Acc) ->
|
||||
maybe_add_unavailable_guild(GuildId, ExistingGuildIds, Acc) ->
|
||||
case maps:is_key(GuildId, ExistingGuildIds) of
|
||||
true -> Acc;
|
||||
false -> [#{<<"id">> => integer_to_binary(GuildId), <<"unavailable">> => true} | Acc]
|
||||
false -> [unavailable_guild_placeholder(GuildId) | Acc]
|
||||
end.
|
||||
|
||||
-spec unavailable_guild_placeholder(guild_id()) -> map().
|
||||
unavailable_guild_placeholder(GuildId) ->
|
||||
#{<<"id">> => integer_to_binary(GuildId), <<"unavailable">> => true}.
|
||||
|
||||
-spec collected_guild_id_map([map()]) -> #{guild_id() => true}.
|
||||
collected_guild_id_map(GuildStates) ->
|
||||
lists:foldl(fun accumulate_guild_id/2, #{}, GuildStates).
|
||||
@@ -255,22 +254,25 @@ parse_guild_id_binary(GuildIdBin) ->
|
||||
error:badarg -> error
|
||||
end.
|
||||
|
||||
-spec guilds_for_ready_payload(boolean(), [map()]) -> [map()].
|
||||
guilds_for_ready_payload(true, _AllGuildStates) -> [];
|
||||
guilds_for_ready_payload(false, AllGuildStates) -> AllGuildStates.
|
||||
-spec guilds_for_ready_payload(boolean(), [map()], map()) -> [map()].
|
||||
guilds_for_ready_payload(true, _CollectedGuilds, Guilds) ->
|
||||
[unavailable_guild_placeholder(GuildId) || GuildId <- maps:keys(Guilds)];
|
||||
guilds_for_ready_payload(false, CollectedGuilds, Guilds) ->
|
||||
build_all_guild_states(CollectedGuilds, Guilds).
|
||||
|
||||
-spec dispatch_bot_guild_creates(boolean(), [map()], map(), session_state()) -> session_state().
|
||||
dispatch_bot_guild_creates(false, _CollectedGuilds, _Guilds, State) ->
|
||||
State;
|
||||
dispatch_bot_guild_creates(true, CollectedGuilds, _Guilds, State) ->
|
||||
dispatch_bot_guild_creates(true, CollectedGuilds, Guilds, State) ->
|
||||
AllGuildStates = strip_collected_guild_states(CollectedGuilds),
|
||||
lists:foldl(
|
||||
fun(GuildState, AccState) ->
|
||||
dispatch_event(guild_state_event(GuildState), GuildState, AccState)
|
||||
end,
|
||||
State,
|
||||
AllGuildStates
|
||||
).
|
||||
AnnouncedState = session_bot_guilds:forget_joins(maps:keys(Guilds), State),
|
||||
lists:foldl(fun dispatch_bot_guild_state/2, AnnouncedState, AllGuildStates).
|
||||
|
||||
-spec dispatch_bot_guild_state(map(), session_state()) -> session_state().
|
||||
dispatch_bot_guild_state(GuildState, State) ->
|
||||
Event = guild_state_event(GuildState),
|
||||
{Data, NextState} = session_bot_guilds:guild_event(Event, GuildState, State),
|
||||
dispatch_event(Event, Data, NextState).
|
||||
|
||||
-spec schedule_call_creates(session_state(), binary()) -> ok.
|
||||
schedule_call_creates(State, SessionId) ->
|
||||
|
||||
@@ -32,6 +32,118 @@ process_guild_state_available_dispatches_guild_create_test() ->
|
||||
?assertEqual(guild_create, FirstEvent),
|
||||
ok.
|
||||
|
||||
process_guild_state_bot_guild_create_marks_guild_available_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = (base_state_for_guild_dispatch_test())#{bot => true, socket_pid => self()},
|
||||
GuildState = #{<<"id">> => <<"123">>, <<"channels">> => [], <<"members">> => []},
|
||||
{noreply, _State1} = session_ready:process_guild_state(GuildState, State0),
|
||||
receive
|
||||
{dispatch, guild_create, GuildCreateData, 1} ->
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, GuildCreateData)),
|
||||
?assertEqual(false, maps:get(<<"unavailable">>, GuildCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end.
|
||||
|
||||
process_guild_state_bot_guild_create_after_join_omits_unavailable_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = (base_state_for_guild_dispatch_test())#{bot => true, socket_pid => self()},
|
||||
{noreply, State1} = session:handle_cast({guild_join, 123}, State0),
|
||||
GuildState = #{<<"id">> => <<"123">>, <<"channels">> => [], <<"members">> => []},
|
||||
{noreply, State2} = session_ready:process_guild_state(GuildState, State1),
|
||||
receive
|
||||
{dispatch, guild_create, JoinCreateData, 1} ->
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, JoinCreateData)),
|
||||
?assertNot(maps:is_key(<<"unavailable">>, JoinCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end,
|
||||
{noreply, _State3} = session_ready:process_guild_state(GuildState, State2),
|
||||
receive
|
||||
{dispatch, guild_create, LaterCreateData, 2} ->
|
||||
?assertEqual(false, maps:get(<<"unavailable">>, LaterCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, second_guild_create_not_dispatched)
|
||||
end,
|
||||
drain_mailbox().
|
||||
|
||||
process_guild_state_bot_guild_create_after_join_and_outage_omits_unavailable_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = (base_state_for_guild_dispatch_test())#{bot => true, socket_pid => self()},
|
||||
{noreply, State1} = session:handle_cast({guild_join, 123}, State0),
|
||||
{noreply, State2} = session_ready:mark_guild_unavailable(123, State1),
|
||||
receive
|
||||
{dispatch, guild_delete, GuildDeleteData, 1} ->
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, GuildDeleteData)),
|
||||
?assertEqual(true, maps:get(<<"unavailable">>, GuildDeleteData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_delete_not_dispatched)
|
||||
end,
|
||||
GuildState = #{<<"id">> => <<"123">>, <<"channels">> => [], <<"members">> => []},
|
||||
{noreply, _State3} = session_ready:process_guild_state(GuildState, State2),
|
||||
receive
|
||||
{dispatch, guild_create, GuildCreateData, 2} ->
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, GuildCreateData)),
|
||||
?assertNot(maps:is_key(<<"unavailable">>, GuildCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end,
|
||||
drain_mailbox().
|
||||
|
||||
process_guild_state_bot_guild_create_after_join_and_removal_marks_guild_available_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = (base_state_for_guild_dispatch_test())#{
|
||||
bot => true,
|
||||
socket_pid => self(),
|
||||
user_id => 1
|
||||
},
|
||||
{noreply, State1} = session:handle_cast({guild_join, 123}, State0),
|
||||
{noreply, State2} = session_connection_guild:handle_result_internal(
|
||||
123, 3, {error, not_member}, State1
|
||||
),
|
||||
receive
|
||||
{dispatch, guild_delete, GuildDeleteData, 1} ->
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, GuildDeleteData)),
|
||||
?assertNot(maps:is_key(<<"unavailable">>, GuildDeleteData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_delete_not_dispatched)
|
||||
end,
|
||||
GuildState = #{<<"id">> => <<"123">>, <<"channels">> => [], <<"members">> => []},
|
||||
{noreply, _State3} = session_ready:process_guild_state(GuildState, State2),
|
||||
receive
|
||||
{dispatch, guild_create, GuildCreateData, 2} ->
|
||||
?assertEqual(false, maps:get(<<"unavailable">>, GuildCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end,
|
||||
drain_mailbox().
|
||||
|
||||
relayed_guild_create_to_bot_marks_guild_available_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = (base_state_for_guild_dispatch_test())#{bot => true, socket_pid => self()},
|
||||
GuildState = #{<<"id">> => <<"123">>, <<"channels">> => [], <<"members">> => []},
|
||||
{noreply, _State1} = session:handle_cast({dispatch, guild_create, GuildState}, State0),
|
||||
receive
|
||||
{dispatch, guild_create, GuildCreateData, 1} ->
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, GuildCreateData)),
|
||||
?assertEqual(false, maps:get(<<"unavailable">>, GuildCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end.
|
||||
|
||||
process_guild_state_user_guild_create_omits_unavailable_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = (base_state_for_guild_dispatch_test())#{bot => false, socket_pid => self()},
|
||||
GuildState = #{<<"id">> => <<"123">>, <<"channels">> => [], <<"members">> => []},
|
||||
{noreply, _State1} = session_ready:process_guild_state(GuildState, State0),
|
||||
receive
|
||||
{dispatch, guild_create, GuildCreateData, 1} ->
|
||||
?assertEqual(<<"123">>, maps:get(<<"id">>, GuildCreateData)),
|
||||
?assertNot(maps:is_key(<<"unavailable">>, GuildCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end.
|
||||
|
||||
mark_guild_unavailable_hidden_includes_flag_test() ->
|
||||
State0 = #{
|
||||
collected_guild_states => [],
|
||||
@@ -88,15 +200,20 @@ dispatch_ready_data_bot_unavailable_dispatches_guild_delete_test() ->
|
||||
),
|
||||
{noreply, _State1} = session_ready:dispatch_ready_data(State0),
|
||||
receive
|
||||
{dispatch, ready, ReadyData, _ReadySeq} ->
|
||||
?assertEqual([], maps:get(<<"guilds">>, ReadyData, []));
|
||||
{dispatch, ready, ReadyData, ReadySeq} ->
|
||||
?assertEqual(1, ReadySeq),
|
||||
?assertEqual(
|
||||
[#{<<"id">> => <<"987">>, <<"unavailable">> => true}],
|
||||
maps:get(<<"guilds">>, ReadyData)
|
||||
);
|
||||
OtherReady ->
|
||||
?assert(false, {unexpected_ready_message, OtherReady})
|
||||
after 1000 ->
|
||||
?assert(false, ready_not_dispatched)
|
||||
end,
|
||||
receive
|
||||
{dispatch, guild_delete, GuildDeleteData, _GuildDeleteSeq} ->
|
||||
{dispatch, guild_delete, GuildDeleteData, GuildDeleteSeq} ->
|
||||
?assertEqual(2, GuildDeleteSeq),
|
||||
?assertEqual(<<"987">>, maps:get(<<"id">>, GuildDeleteData)),
|
||||
?assertEqual(true, maps:get(<<"unavailable">>, GuildDeleteData));
|
||||
OtherDelete ->
|
||||
@@ -206,31 +323,154 @@ dispatch_ready_data_nonbot_replaces_unavailable_placeholder_with_collected_guild
|
||||
?assert(false, ready_not_dispatched)
|
||||
end.
|
||||
|
||||
dispatch_ready_data_bot_does_not_synthesize_unavailable_placeholders_test() ->
|
||||
dispatch_ready_data_bot_lists_every_guild_as_unavailable_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = base_ready_state(
|
||||
<<"session-ready-bot-placeholder-test">>,
|
||||
47,
|
||||
true,
|
||||
#{111 => undefined, 222 => cached_unavailable},
|
||||
#{
|
||||
111 => undefined,
|
||||
222 => cached_unavailable,
|
||||
333 => unavailable,
|
||||
444 => {self(), make_ref()}
|
||||
},
|
||||
[]
|
||||
),
|
||||
{noreply, _State1} = session_ready:dispatch_ready_data(State0),
|
||||
receive
|
||||
{dispatch, ready, ReadyData, _ReadySeq} ->
|
||||
?assertEqual([], maps:get(<<"guilds">>, ReadyData, []));
|
||||
{dispatch, ready, ReadyData, ReadySeq} ->
|
||||
?assertEqual(1, ReadySeq),
|
||||
?assertEqual(
|
||||
[
|
||||
#{<<"id">> => <<"111">>, <<"unavailable">> => true},
|
||||
#{<<"id">> => <<"222">>, <<"unavailable">> => true},
|
||||
#{<<"id">> => <<"333">>, <<"unavailable">> => true},
|
||||
#{<<"id">> => <<"444">>, <<"unavailable">> => true}
|
||||
],
|
||||
lists:sort(maps:get(<<"guilds">>, ReadyData))
|
||||
);
|
||||
OtherReady ->
|
||||
?assert(false, {unexpected_ready_message, OtherReady})
|
||||
after 1000 ->
|
||||
?assert(false, ready_not_dispatched)
|
||||
end,
|
||||
receive
|
||||
{dispatch, guild_create, _GuildCreateData, _GuildCreateSeq} ->
|
||||
?assert(false, unexpected_guild_create_for_bot_ready);
|
||||
{dispatch, guild_delete, _GuildDeleteData, _GuildDeleteSeq} ->
|
||||
?assert(false, unexpected_synthesized_unavailable_guild)
|
||||
?assert(false, unexpected_guild_delete_for_bot_ready)
|
||||
after 100 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
dispatch_ready_data_bot_lists_collected_guild_as_unavailable_stub_test() ->
|
||||
drain_mailbox(),
|
||||
CollectedGuild = #{
|
||||
<<"id">> => <<"111">>,
|
||||
<<"name">> => <<"guild-111">>,
|
||||
<<"members">> => [
|
||||
#{<<"user">> => #{<<"id">> => <<"1">>, <<"username">> => <<"member-1">>}}
|
||||
],
|
||||
<<"channels">> => []
|
||||
},
|
||||
State0 = base_ready_state(
|
||||
<<"session-ready-bot-collected-test">>,
|
||||
50,
|
||||
true,
|
||||
#{111 => undefined, 222 => undefined},
|
||||
[CollectedGuild]
|
||||
),
|
||||
{noreply, _State1} = session_ready:dispatch_ready_data(State0),
|
||||
receive
|
||||
{dispatch, ready, ReadyData, ReadySeq} ->
|
||||
?assertEqual(1, ReadySeq),
|
||||
?assertEqual(
|
||||
[
|
||||
#{<<"id">> => <<"111">>, <<"unavailable">> => true},
|
||||
#{<<"id">> => <<"222">>, <<"unavailable">> => true}
|
||||
],
|
||||
lists:sort(maps:get(<<"guilds">>, ReadyData))
|
||||
);
|
||||
OtherReady ->
|
||||
?assert(false, {unexpected_ready_message, OtherReady})
|
||||
after 1000 ->
|
||||
?assert(false, ready_not_dispatched)
|
||||
end,
|
||||
receive
|
||||
{dispatch, guild_create, GuildCreateData, GuildCreateSeq} ->
|
||||
?assertEqual(2, GuildCreateSeq),
|
||||
?assertEqual(<<"111">>, maps:get(<<"id">>, GuildCreateData)),
|
||||
?assertEqual(<<"guild-111">>, maps:get(<<"name">>, GuildCreateData)),
|
||||
?assertEqual(false, maps:get(<<"unavailable">>, GuildCreateData)),
|
||||
?assertEqual(
|
||||
[#{<<"user">> => #{<<"id">> => <<"1">>}}],
|
||||
maps:get(<<"members">>, GuildCreateData)
|
||||
);
|
||||
OtherGuild ->
|
||||
?assert(false, {unexpected_guild_event, OtherGuild})
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end,
|
||||
receive
|
||||
{dispatch, OtherEvent, _OtherData, _OtherSeq} ->
|
||||
?assert(false, {unexpected_dispatch_after_burst, OtherEvent})
|
||||
after 100 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
dispatch_ready_data_bot_marks_guilds_joined_before_ready_available_test() ->
|
||||
drain_mailbox(),
|
||||
CollectedGuild = #{<<"id">> => <<"111">>, <<"channels">> => [], <<"members">> => []},
|
||||
UnavailableGuild = #{<<"id">> => <<"222">>, <<"unavailable">> => true},
|
||||
State0 = base_ready_state(
|
||||
<<"session-ready-bot-join-test">>,
|
||||
51,
|
||||
true,
|
||||
#{111 => {self(), make_ref()}, 222 => unavailable},
|
||||
[UnavailableGuild, CollectedGuild]
|
||||
),
|
||||
{noreply, State1} = session:handle_cast({guild_join, 111}, State0),
|
||||
{noreply, State2} = session:handle_cast({guild_join, 222}, State1),
|
||||
drain_mailbox(),
|
||||
{noreply, State3} = session_ready:dispatch_ready_data(State2),
|
||||
receive
|
||||
{dispatch, ready, ReadyData, 1} ->
|
||||
?assertEqual(
|
||||
[
|
||||
#{<<"id">> => <<"111">>, <<"unavailable">> => true},
|
||||
#{<<"id">> => <<"222">>, <<"unavailable">> => true}
|
||||
],
|
||||
lists:sort(maps:get(<<"guilds">>, ReadyData))
|
||||
)
|
||||
after 1000 ->
|
||||
?assert(false, ready_not_dispatched)
|
||||
end,
|
||||
receive
|
||||
{dispatch, guild_create, BurstCreateData, 2} ->
|
||||
?assertEqual(<<"111">>, maps:get(<<"id">>, BurstCreateData)),
|
||||
?assertEqual(false, maps:get(<<"unavailable">>, BurstCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_create_not_dispatched)
|
||||
end,
|
||||
receive
|
||||
{dispatch, guild_delete, BurstDeleteData, 3} ->
|
||||
?assertEqual(<<"222">>, maps:get(<<"id">>, BurstDeleteData)),
|
||||
?assertEqual(true, maps:get(<<"unavailable">>, BurstDeleteData))
|
||||
after 1000 ->
|
||||
?assert(false, guild_delete_not_dispatched)
|
||||
end,
|
||||
RecoveredGuild = #{<<"id">> => <<"222">>, <<"channels">> => [], <<"members">> => []},
|
||||
{noreply, _State4} = session_ready:process_guild_state(RecoveredGuild, State3),
|
||||
receive
|
||||
{dispatch, guild_create, RecoveredCreateData, 4} ->
|
||||
?assertEqual(<<"222">>, maps:get(<<"id">>, RecoveredCreateData)),
|
||||
?assertEqual(false, maps:get(<<"unavailable">>, RecoveredCreateData))
|
||||
after 1000 ->
|
||||
?assert(false, recovered_guild_create_not_dispatched)
|
||||
end,
|
||||
drain_mailbox().
|
||||
|
||||
dispatch_ready_data_includes_shard_metadata_test() ->
|
||||
drain_mailbox(),
|
||||
State0 = (base_ready_state(
|
||||
|
||||
Reference in New Issue
Block a user