mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(gateway): resync permissions and validate voice leaves (#2492)
This commit is contained in:
@@ -96,6 +96,7 @@ handle_post_update(guild_member_update, EventData, OldState, NewState) ->
|
|||||||
guild_state_member:sync_member(EventData, NewState),
|
guild_state_member:sync_member(EventData, NewState),
|
||||||
UserId = guild_state_member:extract_user_id(EventData),
|
UserId = guild_state_member:extract_user_id(EventData),
|
||||||
State1 = dispatch_member_update_visibility(UserId, OldState, NewState),
|
State1 = dispatch_member_update_visibility(UserId, OldState, NewState),
|
||||||
|
ok = guild_voice_permission_sync:maybe_sync_permissions_on_member_update(EventData, State1),
|
||||||
refresh_member_session_cache(UserId, State1);
|
refresh_member_session_cache(UserId, State1);
|
||||||
handle_post_update(Event, EventData, OldState, NewState) when
|
handle_post_update(Event, EventData, OldState, NewState) when
|
||||||
Event =:= guild_role_create;
|
Event =:= guild_role_create;
|
||||||
@@ -122,15 +123,25 @@ post_update_role(guild_role_create, _EventData, _OldState, NewState) ->
|
|||||||
post_update_role(guild_role_update, EventData, OldState, NewState) ->
|
post_update_role(guild_role_update, EventData, OldState, NewState) ->
|
||||||
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
||||||
RoleIds = guild_state_roles:extract_role_ids_from_role_update(EventData),
|
RoleIds = guild_state_roles:extract_role_ids_from_role_update(EventData),
|
||||||
guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState1);
|
resync_roles_after_permission_change(RoleIds, OldState, NewState1);
|
||||||
post_update_role(guild_role_update_bulk, EventData, OldState, NewState) ->
|
post_update_role(guild_role_update_bulk, EventData, OldState, NewState) ->
|
||||||
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
||||||
RoleIds = guild_state_roles:extract_role_ids_from_role_update_bulk(EventData),
|
RoleIds = guild_state_roles:extract_role_ids_from_role_update_bulk(EventData),
|
||||||
guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState1);
|
resync_roles_after_permission_change(RoleIds, OldState, NewState1);
|
||||||
post_update_role(guild_role_delete, EventData, OldState, NewState) ->
|
post_update_role(guild_role_delete, EventData, OldState, NewState) ->
|
||||||
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
||||||
RoleIds = guild_state_roles:extract_role_ids_from_role_delete(EventData),
|
RoleIds = guild_state_roles:extract_role_ids_from_role_delete(EventData),
|
||||||
guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState1).
|
resync_roles_after_permission_change(RoleIds, OldState, NewState1).
|
||||||
|
|
||||||
|
-spec resync_roles_after_permission_change([integer()], guild_state(), guild_state()) ->
|
||||||
|
guild_state().
|
||||||
|
resync_roles_after_permission_change(RoleIds, OldState, NewState) ->
|
||||||
|
Recomputed = guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState),
|
||||||
|
lists:foreach(
|
||||||
|
fun(RoleId) -> guild_voice_permission_sync:sync_users_with_role(RoleId, Recomputed) end,
|
||||||
|
RoleIds
|
||||||
|
),
|
||||||
|
Recomputed.
|
||||||
|
|
||||||
-spec post_update_channel(event(), event_data(), guild_state(), guild_state()) -> guild_state().
|
-spec post_update_channel(event(), event_data(), guild_state(), guild_state()) -> guild_state().
|
||||||
post_update_channel(channel_create, _EventData, _OldState, NewState) ->
|
post_update_channel(channel_create, _EventData, _OldState, NewState) ->
|
||||||
@@ -138,24 +149,31 @@ post_update_channel(channel_create, _EventData, _OldState, NewState) ->
|
|||||||
NewState;
|
NewState;
|
||||||
post_update_channel(channel_update, EventData, OldState, NewState) ->
|
post_update_channel(channel_update, EventData, OldState, NewState) ->
|
||||||
ChanIds = guild_state_channels:extract_channel_ids_from_channel_update(EventData),
|
ChanIds = guild_state_channels:extract_channel_ids_from_channel_update(EventData),
|
||||||
NewState1 = guild_member_list_write:rebuild_channels_for_permission_change(
|
resync_channels_after_permission_change(ChanIds, OldState, NewState);
|
||||||
ChanIds, NewState
|
|
||||||
),
|
|
||||||
guild_visibility:compute_and_dispatch_visibility_changes_for_channels(
|
|
||||||
ChanIds, OldState, NewState1
|
|
||||||
);
|
|
||||||
post_update_channel(channel_update_bulk, EventData, OldState, NewState) ->
|
post_update_channel(channel_update_bulk, EventData, OldState, NewState) ->
|
||||||
ChanIds = guild_state_channels:extract_channel_ids_from_channel_update_bulk(EventData),
|
ChanIds = guild_state_channels:extract_channel_ids_from_channel_update_bulk(EventData),
|
||||||
NewState1 = guild_member_list_write:rebuild_channels_for_permission_change(
|
resync_channels_after_permission_change(ChanIds, OldState, NewState);
|
||||||
ChanIds, NewState
|
|
||||||
),
|
|
||||||
guild_visibility:compute_and_dispatch_visibility_changes_for_channels(
|
|
||||||
ChanIds, OldState, NewState1
|
|
||||||
);
|
|
||||||
post_update_channel(channel_delete, _EventData, _OldState, NewState) ->
|
post_update_channel(channel_delete, _EventData, _OldState, NewState) ->
|
||||||
maybe_sync_member_list_permission_state(NewState),
|
maybe_sync_member_list_permission_state(NewState),
|
||||||
NewState.
|
NewState.
|
||||||
|
|
||||||
|
-spec resync_channels_after_permission_change([integer()], guild_state(), guild_state()) ->
|
||||||
|
guild_state().
|
||||||
|
resync_channels_after_permission_change(ChanIds, OldState, NewState) ->
|
||||||
|
Rebuilt = guild_member_list_write:rebuild_channels_for_permission_change(ChanIds, NewState),
|
||||||
|
Dispatched = guild_visibility:compute_and_dispatch_visibility_changes_for_channels(
|
||||||
|
ChanIds, OldState, Rebuilt
|
||||||
|
),
|
||||||
|
lists:foreach(
|
||||||
|
fun(ChanId) ->
|
||||||
|
guild_voice_permission_sync:sync_all_voice_permissions_for_channel(
|
||||||
|
ChanId, Dispatched
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
ChanIds
|
||||||
|
),
|
||||||
|
Dispatched.
|
||||||
|
|
||||||
-spec post_update_member_remove(event_data(), guild_state()) -> guild_state().
|
-spec post_update_member_remove(event_data(), guild_state()) -> guild_state().
|
||||||
post_update_member_remove(EventData, NewState) ->
|
post_update_member_remove(EventData, NewState) ->
|
||||||
UserId = guild_state_member:extract_user_id(EventData),
|
UserId = guild_state_member:extract_user_id(EventData),
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ handle_member_voice(Context, Member, VoiceStates, State) ->
|
|||||||
voice_reply().
|
voice_reply().
|
||||||
handle_disconnect(Context, VoiceStates, State) ->
|
handle_disconnect(Context, VoiceStates, State) ->
|
||||||
guild_voice_disconnect:handle_voice_disconnect(
|
guild_voice_disconnect:handle_voice_disconnect(
|
||||||
maps:get(raw_connection_id, Context),
|
maps:get(connection_id, Context),
|
||||||
maps:get(session_id, Context),
|
maps:get(session_id, Context),
|
||||||
maps:get(user_id, Context),
|
maps:get(user_id, Context),
|
||||||
VoiceStates,
|
VoiceStates,
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ build_context(Request0) ->
|
|||||||
Request = map_utils:ensure_map(Request0),
|
Request = map_utils:ensure_map(Request0),
|
||||||
Norm = fun guild_voice_connection_normalize:normalize_boolean/1,
|
Norm = fun guild_voice_connection_normalize:normalize_boolean/1,
|
||||||
Coord = fun guild_voice_connection_normalize:normalize_coordinate/1,
|
Coord = fun guild_voice_connection_normalize:normalize_coordinate/1,
|
||||||
RawConnectionId = maps:get(connection_id, Request, undefined),
|
|
||||||
#{
|
#{
|
||||||
user_id => guild_voice_connection_normalize:normalize_user_id(
|
user_id => guild_voice_connection_normalize:normalize_user_id(
|
||||||
maps:get(user_id, Request, undefined)
|
maps:get(user_id, Request, undefined)
|
||||||
@@ -53,9 +52,8 @@ build_context(Request0) ->
|
|||||||
),
|
),
|
||||||
session_id => maps:get(session_id, Request, undefined),
|
session_id => maps:get(session_id, Request, undefined),
|
||||||
connection_id => guild_voice_connection_normalize:normalize_connection_id(
|
connection_id => guild_voice_connection_normalize:normalize_connection_id(
|
||||||
RawConnectionId
|
maps:get(connection_id, Request, undefined)
|
||||||
),
|
),
|
||||||
raw_connection_id => RawConnectionId,
|
|
||||||
self_mute => Norm(maps:get(self_mute, Request, false)),
|
self_mute => Norm(maps:get(self_mute, Request, false)),
|
||||||
self_deaf => Norm(maps:get(self_deaf, Request, false)),
|
self_deaf => Norm(maps:get(self_deaf, Request, false)),
|
||||||
self_video => Norm(maps:get(self_video, Request, false)),
|
self_video => Norm(maps:get(self_video, Request, false)),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
-export([
|
-export([
|
||||||
sync_user_voice_permissions/2,
|
sync_user_voice_permissions/2,
|
||||||
sync_all_voice_permissions_for_channel/2,
|
sync_all_voice_permissions_for_channel/2,
|
||||||
|
sync_users_with_role/2,
|
||||||
maybe_sync_permissions_on_role_update/2,
|
maybe_sync_permissions_on_role_update/2,
|
||||||
maybe_sync_permissions_on_member_update/2
|
maybe_sync_permissions_on_member_update/2
|
||||||
]).
|
]).
|
||||||
@@ -129,6 +130,7 @@ sync_validated_voice_state(GuildId, UserId, ChId, ConnId, VoiceState, State) ->
|
|||||||
VoicePermissions = VoicePermissions0#{
|
VoicePermissions = VoicePermissions0#{
|
||||||
deaf => maps:get(<<"deaf">>, VoiceState, false)
|
deaf => maps:get(<<"deaf">>, VoiceState, false)
|
||||||
},
|
},
|
||||||
|
ok = maybe_clear_self_stream(ChId, VoiceState, VoicePermissions, State),
|
||||||
dispatch_permission_update(
|
dispatch_permission_update(
|
||||||
GuildId, ChId, UserId, ConnId, VoicePermissions, State
|
GuildId, ChId, UserId, ConnId, VoicePermissions, State
|
||||||
);
|
);
|
||||||
@@ -136,6 +138,27 @@ sync_validated_voice_state(GuildId, UserId, ChId, ConnId, VoiceState, State) ->
|
|||||||
dispatch_force_disconnect(GuildId, ChId, UserId, ConnId, State)
|
dispatch_force_disconnect(GuildId, ChId, UserId, ConnId, State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec maybe_clear_self_stream(
|
||||||
|
channel_id(), voice_state(), voice_utils:voice_permissions(), guild_state()
|
||||||
|
) -> ok.
|
||||||
|
maybe_clear_self_stream(ChId, VoiceState, #{can_stream := false}, State) ->
|
||||||
|
SelfStream = maps:get(<<"self_stream">>, VoiceState, false),
|
||||||
|
SelfVideo = maps:get(<<"self_video">>, VoiceState, false),
|
||||||
|
case SelfStream =:= true orelse SelfVideo =:= true of
|
||||||
|
false ->
|
||||||
|
ok;
|
||||||
|
true ->
|
||||||
|
Cleared = VoiceState#{
|
||||||
|
<<"self_stream">> => false,
|
||||||
|
<<"self_video">> => false
|
||||||
|
},
|
||||||
|
guild_voice_broadcast:broadcast_voice_state_update(
|
||||||
|
Cleared, State, integer_to_binary(ChId)
|
||||||
|
)
|
||||||
|
end;
|
||||||
|
maybe_clear_self_stream(_ChId, _VoiceState, _VoicePermissions, _State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
-spec user_has_base_voice_access(user_id(), channel_id(), guild_state()) -> boolean().
|
-spec user_has_base_voice_access(user_id(), channel_id(), guild_state()) -> boolean().
|
||||||
user_has_base_voice_access(UserId, ChannelId, State) ->
|
user_has_base_voice_access(UserId, ChannelId, State) ->
|
||||||
case guild_virtual_channel_access:has_virtual_access(UserId, ChannelId, State) of
|
case guild_virtual_channel_access:has_virtual_access(UserId, ChannelId, State) of
|
||||||
@@ -319,6 +342,7 @@ build_sync_test_data(GuildId, RoleId, UserId, ChannelId, Permissions) ->
|
|||||||
}.
|
}.
|
||||||
|
|
||||||
sync_user_voice_permissions_syncs_connected_user_test() ->
|
sync_user_voice_permissions_syncs_connected_user_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
TestFun = make_sync_test_fun(),
|
TestFun = make_sync_test_fun(),
|
||||||
State = build_sync_test_state(TestFun),
|
State = build_sync_test_state(TestFun),
|
||||||
ok = sync_user_voice_permissions(10, State),
|
ok = sync_user_voice_permissions(10, State),
|
||||||
@@ -393,6 +417,7 @@ maybe_sync_permissions_on_role_update_uses_role_index_test() ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
sync_disconnects_when_connect_permission_lost_test() ->
|
sync_disconnects_when_connect_permission_lost_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
TestFun = make_sync_test_fun(),
|
TestFun = make_sync_test_fun(),
|
||||||
UserId = 10,
|
UserId = 10,
|
||||||
ChannelId = 500,
|
ChannelId = 500,
|
||||||
@@ -410,6 +435,80 @@ sync_disconnects_when_connect_permission_lost_test() ->
|
|||||||
?assert(false)
|
?assert(false)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
sync_clears_self_stream_when_stream_permission_lost_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
|
TestFun = make_sync_test_fun(),
|
||||||
|
UserId = 10,
|
||||||
|
ChannelId = 500,
|
||||||
|
GuildId = 42,
|
||||||
|
RoleId = 999,
|
||||||
|
WithoutStream =
|
||||||
|
constants:view_channel_permission() bor
|
||||||
|
constants:connect_permission() bor
|
||||||
|
constants:speak_permission(),
|
||||||
|
State = build_streaming_sync_test_state(
|
||||||
|
TestFun, GuildId, RoleId, UserId, ChannelId, WithoutStream
|
||||||
|
),
|
||||||
|
ok = sync_user_voice_permissions(UserId, State),
|
||||||
|
Broadcast = receive_voice_state_dispatch(),
|
||||||
|
?assertEqual(false, maps:get(<<"self_stream">>, Broadcast)),
|
||||||
|
?assertEqual(false, maps:get(<<"self_video">>, Broadcast)),
|
||||||
|
?assertEqual(<<"test-conn">>, maps:get(<<"connection_id">>, Broadcast)).
|
||||||
|
|
||||||
|
sync_keeps_self_stream_when_stream_permission_held_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
|
TestFun = make_sync_test_fun(),
|
||||||
|
UserId = 10,
|
||||||
|
ChannelId = 500,
|
||||||
|
GuildId = 42,
|
||||||
|
RoleId = 999,
|
||||||
|
WithStream =
|
||||||
|
constants:view_channel_permission() bor
|
||||||
|
constants:connect_permission() bor
|
||||||
|
constants:speak_permission() bor
|
||||||
|
constants:stream_permission(),
|
||||||
|
State = build_streaming_sync_test_state(
|
||||||
|
TestFun, GuildId, RoleId, UserId, ChannelId, WithStream
|
||||||
|
),
|
||||||
|
ok = sync_user_voice_permissions(UserId, State),
|
||||||
|
?assertEqual(undefined, receive_optional_voice_state_dispatch()).
|
||||||
|
|
||||||
|
build_streaming_sync_test_state(TestFun, GuildId, RoleId, UserId, ChannelId, Permissions) ->
|
||||||
|
VoiceState = #{
|
||||||
|
<<"user_id">> => integer_to_binary(UserId),
|
||||||
|
<<"channel_id">> => integer_to_binary(ChannelId),
|
||||||
|
<<"connection_id">> => <<"test-conn">>,
|
||||||
|
<<"self_stream">> => true,
|
||||||
|
<<"self_video">> => true
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
id => GuildId,
|
||||||
|
voice_states => #{<<"conn">> => VoiceState},
|
||||||
|
sessions => #{
|
||||||
|
<<"s1">> => #{
|
||||||
|
pid => self(),
|
||||||
|
user_id => UserId,
|
||||||
|
viewable_channels => #{ChannelId => true}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
test_permission_sync_fun => TestFun,
|
||||||
|
data => build_sync_test_data(GuildId, RoleId, UserId, ChannelId, Permissions)
|
||||||
|
}.
|
||||||
|
|
||||||
|
receive_voice_state_dispatch() ->
|
||||||
|
case receive_optional_voice_state_dispatch() of
|
||||||
|
undefined -> error(voice_state_update_not_received);
|
||||||
|
Payload -> Payload
|
||||||
|
end.
|
||||||
|
|
||||||
|
receive_optional_voice_state_dispatch() ->
|
||||||
|
receive
|
||||||
|
{'$gen_cast', {dispatch, voice_state_update, {pre_encoded, Bin}}} -> json:decode(Bin);
|
||||||
|
{'$gen_cast', {dispatch, voice_state_update, Payload}} -> Payload
|
||||||
|
after 200 ->
|
||||||
|
undefined
|
||||||
|
end.
|
||||||
|
|
||||||
make_sync_test_fun() ->
|
make_sync_test_fun() ->
|
||||||
Self = self(),
|
Self = self(),
|
||||||
fun(GId, ChId, UId, ConnId, Perms) ->
|
fun(GId, ChId, UId, ConnId, Perms) ->
|
||||||
@@ -461,4 +560,11 @@ role_sync_test_data(
|
|||||||
]
|
]
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
drain_mailbox() ->
|
||||||
|
receive
|
||||||
|
_ -> drain_mailbox()
|
||||||
|
after 0 ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|||||||
@@ -40,7 +40,21 @@ apply_voice_permissions_to_flags(Flags, VoicePermissions) ->
|
|||||||
false -> true;
|
false -> true;
|
||||||
_ -> false
|
_ -> false
|
||||||
end,
|
end,
|
||||||
Flags#{suppress => Suppress}.
|
SelfStream =
|
||||||
|
case maps:get(can_stream, VoicePermissions, true) of
|
||||||
|
false -> false;
|
||||||
|
_ -> maps:get(self_stream, Flags, false) =:= true
|
||||||
|
end,
|
||||||
|
SelfVideo =
|
||||||
|
case maps:get(can_video, VoicePermissions, true) of
|
||||||
|
false -> false;
|
||||||
|
_ -> maps:get(self_video, Flags, false) =:= true
|
||||||
|
end,
|
||||||
|
Flags#{
|
||||||
|
suppress => Suppress,
|
||||||
|
self_stream => SelfStream,
|
||||||
|
self_video => SelfVideo
|
||||||
|
}.
|
||||||
|
|
||||||
-spec build_voice_token_rpc_request(
|
-spec build_voice_token_rpc_request(
|
||||||
integer() | null,
|
integer() | null,
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
-module(guild_state_channel_resync_tests).
|
||||||
|
-typing([eqwalizer]).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
-define(GUILD_ID, 9100).
|
||||||
|
-define(CHANNEL_ID, 9500).
|
||||||
|
-define(ROLE_ID, 9200).
|
||||||
|
-define(USER_A, 9010).
|
||||||
|
-define(USER_B, 9020).
|
||||||
|
-define(USER_C, 9030).
|
||||||
|
|
||||||
|
channel_update_syncs_every_subscriber_after_permission_change_test() ->
|
||||||
|
with_relay_mock(fun run_channel_update_syncs_every_subscriber/0).
|
||||||
|
|
||||||
|
channel_update_bulk_syncs_every_subscriber_after_permission_change_test() ->
|
||||||
|
with_relay_mock(fun run_channel_update_bulk_syncs_every_subscriber/0).
|
||||||
|
|
||||||
|
run_channel_update_syncs_every_subscriber() ->
|
||||||
|
run_resync_case(channel_update, denied_channel()).
|
||||||
|
|
||||||
|
run_channel_update_bulk_syncs_every_subscriber() ->
|
||||||
|
run_resync_case(channel_update_bulk, #{<<"channels">> => [denied_channel()]}).
|
||||||
|
|
||||||
|
run_resync_case(Event, EventData) ->
|
||||||
|
PidA = spawn(fun idle/0),
|
||||||
|
PidB = spawn(fun idle/0),
|
||||||
|
SubsTab = guild_member_list_subs:new(),
|
||||||
|
State0 = base_state(SubsTab, PidA, PidB),
|
||||||
|
{State1, _SyncA, _RangesA} = guild_member_list:subscribe_ranges(
|
||||||
|
<<"s_a">>, list_id(), [{0, 99}], State0
|
||||||
|
),
|
||||||
|
{State2, _SyncB, _RangesB} = guild_member_list:subscribe_ranges(
|
||||||
|
<<"s_b">>, list_id(), [{0, 99}], State1
|
||||||
|
),
|
||||||
|
try
|
||||||
|
?assertEqual({3, 0}, guild_member_list:get_counts(list_id(), State2)),
|
||||||
|
{noreply, UpdatedState} = guild_dispatch:handle_dispatch(Event, EventData, State2),
|
||||||
|
?assertEqual({2, 0}, guild_member_list:get_counts(list_id(), UpdatedState)),
|
||||||
|
{Pids, Raw, Payload} = receive_member_list_dispatch(),
|
||||||
|
?assertEqual(lists:sort([PidA, PidB]), lists:sort(Pids)),
|
||||||
|
?assertEqual(list_id(), maps:get(<<"channel_id">>, Payload)),
|
||||||
|
?assertEqual(list_id(), maps:get(<<"id">>, Payload)),
|
||||||
|
?assertEqual(2, maps:get(<<"member_count">>, Payload)),
|
||||||
|
?assertEqual(nomatch, binary:match(Raw, integer_to_binary(?USER_C))),
|
||||||
|
assert_no_further_dispatch(),
|
||||||
|
guild_member_list_channel_engine:destroy_all(UpdatedState)
|
||||||
|
after
|
||||||
|
PidA ! stop,
|
||||||
|
PidB ! stop,
|
||||||
|
ets:delete(SubsTab)
|
||||||
|
end.
|
||||||
|
|
||||||
|
base_state(SubsTab, PidA, PidB) ->
|
||||||
|
#{
|
||||||
|
id => ?GUILD_ID,
|
||||||
|
data => #{
|
||||||
|
<<"guild">> => #{<<"owner_id">> => <<"999">>},
|
||||||
|
<<"roles">> => [
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(?GUILD_ID),
|
||||||
|
<<"permissions">> => integer_to_binary(viewer_permissions())
|
||||||
|
},
|
||||||
|
#{<<"id">> => integer_to_binary(?ROLE_ID), <<"permissions">> => <<"0">>}
|
||||||
|
],
|
||||||
|
<<"members">> => [
|
||||||
|
member(?USER_A, []),
|
||||||
|
member(?USER_B, []),
|
||||||
|
member(?USER_C, [integer_to_binary(?ROLE_ID)])
|
||||||
|
],
|
||||||
|
<<"channels">> => [visible_channel()]
|
||||||
|
},
|
||||||
|
sessions => #{
|
||||||
|
<<"s_a">> => session(<<"s_a">>, ?USER_A, PidA),
|
||||||
|
<<"s_b">> => session(<<"s_b">>, ?USER_B, PidB)
|
||||||
|
},
|
||||||
|
member_presence => #{},
|
||||||
|
member_list_subscriptions => SubsTab
|
||||||
|
}.
|
||||||
|
|
||||||
|
session(SessionId, UserId, Pid) ->
|
||||||
|
#{
|
||||||
|
session_id => SessionId,
|
||||||
|
user_id => UserId,
|
||||||
|
pid => Pid,
|
||||||
|
viewable_channels => #{?CHANNEL_ID => true}
|
||||||
|
}.
|
||||||
|
|
||||||
|
member(UserId, Roles) ->
|
||||||
|
#{
|
||||||
|
<<"user">> => #{
|
||||||
|
<<"id">> => integer_to_binary(UserId),
|
||||||
|
<<"username">> => <<"u", (integer_to_binary(UserId))/binary>>
|
||||||
|
},
|
||||||
|
<<"roles">> => Roles
|
||||||
|
}.
|
||||||
|
|
||||||
|
visible_channel() ->
|
||||||
|
#{<<"id">> => list_id(), <<"type">> => 0, <<"permission_overwrites">> => []}.
|
||||||
|
|
||||||
|
denied_channel() ->
|
||||||
|
#{
|
||||||
|
<<"id">> => list_id(),
|
||||||
|
<<"type">> => 0,
|
||||||
|
<<"permission_overwrites">> => [
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(?ROLE_ID),
|
||||||
|
<<"type">> => 0,
|
||||||
|
<<"allow">> => <<"0">>,
|
||||||
|
<<"deny">> => integer_to_binary(constants:view_channel_permission())
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}.
|
||||||
|
|
||||||
|
viewer_permissions() ->
|
||||||
|
constants:view_channel_permission() bor constants:view_channel_members_permission().
|
||||||
|
|
||||||
|
list_id() ->
|
||||||
|
integer_to_binary(?CHANNEL_ID).
|
||||||
|
|
||||||
|
with_relay_mock(Fun) ->
|
||||||
|
meck:new(gateway_dispatch_relay, [passthrough, no_link]),
|
||||||
|
Parent = self(),
|
||||||
|
meck:expect(
|
||||||
|
gateway_dispatch_relay,
|
||||||
|
dispatch_many,
|
||||||
|
fun
|
||||||
|
(Pids, guild_member_list_update, {pre_encoded, Bin}, GuildId) when is_binary(Bin) ->
|
||||||
|
Parent ! {member_list_dispatch, Pids, guild_member_list_update, GuildId, Bin},
|
||||||
|
ok;
|
||||||
|
(_Pids, _Event, _Payload, _GuildId) ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
),
|
||||||
|
try
|
||||||
|
Fun()
|
||||||
|
after
|
||||||
|
meck:unload(gateway_dispatch_relay)
|
||||||
|
end.
|
||||||
|
|
||||||
|
receive_member_list_dispatch() ->
|
||||||
|
receive
|
||||||
|
{member_list_dispatch, Pids, guild_member_list_update, ?GUILD_ID, Bin} ->
|
||||||
|
{Pids, Bin, json:decode(Bin)}
|
||||||
|
after 1000 ->
|
||||||
|
?assert(false, no_member_list_sync_dispatched),
|
||||||
|
{[], <<>>, #{}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
assert_no_further_dispatch() ->
|
||||||
|
receive
|
||||||
|
{member_list_dispatch, _Pids, _Event, _GuildId, _Bin} = Msg ->
|
||||||
|
?assert(false, {unexpected_member_list_dispatch, Msg})
|
||||||
|
after 0 ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
idle() ->
|
||||||
|
receive
|
||||||
|
stop -> ok
|
||||||
|
after 30000 -> ok
|
||||||
|
end.
|
||||||
@@ -102,6 +102,15 @@ build_context_normalizes_fields_test() ->
|
|||||||
?assertEqual(false, maps:get(self_stream, Context)),
|
?assertEqual(false, maps:get(self_stream, Context)),
|
||||||
?assertEqual(false, maps:get(is_mobile, Context)).
|
?assertEqual(false, maps:get(is_mobile, Context)).
|
||||||
|
|
||||||
|
build_context_normalizes_null_connection_id_test() ->
|
||||||
|
Context = guild_voice_connection_util:build_context(#{
|
||||||
|
user_id => 42,
|
||||||
|
channel_id => null,
|
||||||
|
connection_id => null
|
||||||
|
}),
|
||||||
|
?assertEqual(undefined, maps:get(connection_id, Context)),
|
||||||
|
?assertNot(maps:is_key(raw_connection_id, Context)).
|
||||||
|
|
||||||
build_context_does_not_accept_zero_ids_test() ->
|
build_context_does_not_accept_zero_ids_test() ->
|
||||||
Context = guild_voice_connection_util:build_context(#{
|
Context = guild_voice_connection_util:build_context(#{
|
||||||
user_id => 0,
|
user_id => 0,
|
||||||
|
|||||||
@@ -55,6 +55,30 @@ voice_state_update_invalid_channel_id_test() ->
|
|||||||
#{user_id => 10, channel_id => undefined}, State
|
#{user_id => 10, channel_id => undefined}, State
|
||||||
).
|
).
|
||||||
|
|
||||||
|
voice_state_update_guild_leave_null_connection_id_test() ->
|
||||||
|
State = connected_state(<<"10">>),
|
||||||
|
{reply, {error, validation_error, voice_missing_connection_id}, NewState} =
|
||||||
|
guild_voice_connection:voice_state_update(
|
||||||
|
#{user_id => 10, channel_id => null, connection_id => null}, State
|
||||||
|
),
|
||||||
|
?assertEqual(connected_voice_states(<<"10">>), maps:get(voice_states, NewState)).
|
||||||
|
|
||||||
|
voice_state_update_guild_leave_omitted_connection_id_test() ->
|
||||||
|
State = connected_state(<<"10">>),
|
||||||
|
{reply, {error, validation_error, voice_missing_connection_id}, NewState} =
|
||||||
|
guild_voice_connection:voice_state_update(
|
||||||
|
#{user_id => 10, channel_id => null}, State
|
||||||
|
),
|
||||||
|
?assertEqual(connected_voice_states(<<"10">>), maps:get(voice_states, NewState)).
|
||||||
|
|
||||||
|
voice_state_update_guild_leave_blank_connection_id_test() ->
|
||||||
|
State = connected_state(<<"10">>),
|
||||||
|
assert_voice_state_update_error(
|
||||||
|
#{user_id => 10, channel_id => null, connection_id => <<>>},
|
||||||
|
State,
|
||||||
|
{error, validation_error, voice_missing_connection_id}
|
||||||
|
).
|
||||||
|
|
||||||
voice_state_update_channel_not_found_test() ->
|
voice_state_update_channel_not_found_test() ->
|
||||||
State = replace_channels(base_test_state(), []),
|
State = replace_channels(base_test_state(), []),
|
||||||
{reply, {error, not_found, voice_channel_not_found}, _} =
|
{reply, {error, not_found, voice_channel_not_found}, _} =
|
||||||
@@ -300,6 +324,28 @@ voice_state_update_stress_many_watch_unwatch_updates_test() ->
|
|||||||
),
|
),
|
||||||
?assertMatch(#{<<"conn-1">> := #{}}, maps:get(voice_states, FinalState)).
|
?assertMatch(#{<<"conn-1">> := #{}}, maps:get(voice_states, FinalState)).
|
||||||
|
|
||||||
|
voice_state_update_clears_self_stream_without_stream_permission_test() ->
|
||||||
|
State = connected_state(<<"10">>),
|
||||||
|
{reply, #{success := true}, NewState} = guild_voice_connection:voice_state_update(
|
||||||
|
update_request(#{self_stream => true, self_video => true}), State
|
||||||
|
),
|
||||||
|
Updated = updated_voice_state(NewState),
|
||||||
|
?assertEqual(false, maps:get(<<"self_stream">>, Updated)),
|
||||||
|
?assertEqual(false, maps:get(<<"self_video">>, Updated)).
|
||||||
|
|
||||||
|
voice_state_update_keeps_self_stream_with_stream_permission_test() ->
|
||||||
|
State = maps:put(
|
||||||
|
voice_states,
|
||||||
|
connected_voice_states(<<"10">>),
|
||||||
|
with_stream_permission(base_test_state())
|
||||||
|
),
|
||||||
|
{reply, #{success := true}, NewState} = guild_voice_connection:voice_state_update(
|
||||||
|
update_request(#{self_stream => true, self_video => true}), State
|
||||||
|
),
|
||||||
|
Updated = updated_voice_state(NewState),
|
||||||
|
?assertEqual(true, maps:get(<<"self_stream">>, Updated)),
|
||||||
|
?assertEqual(true, maps:get(<<"self_video">>, Updated)).
|
||||||
|
|
||||||
voice_state_update_allows_twenty_fifth_camera_sharer_test() ->
|
voice_state_update_allows_twenty_fifth_camera_sharer_test() ->
|
||||||
State = camera_state(24),
|
State = camera_state(24),
|
||||||
{reply, #{success := true}, NewState} = guild_voice_connection:voice_state_update(
|
{reply, #{success := true}, NewState} = guild_voice_connection:voice_state_update(
|
||||||
@@ -379,10 +425,20 @@ camera_base_state(SharerCount) ->
|
|||||||
Members = [
|
Members = [
|
||||||
base_test_member(10) | [base_test_member(200 + N) || N <- lists:seq(1, SharerCount)]
|
base_test_member(10) | [base_test_member(200 + N) || N <- lists:seq(1, SharerCount)]
|
||||||
],
|
],
|
||||||
State = base_test_state(),
|
State = with_stream_permission(base_test_state()),
|
||||||
Data0 = maps:get(data, State, #{}),
|
Data0 = maps:get(data, State, #{}),
|
||||||
maps:put(data, maps:put(<<"members">>, Members, Data0), State).
|
maps:put(data, maps:put(<<"members">>, Members, Data0), State).
|
||||||
|
|
||||||
|
with_stream_permission(State) ->
|
||||||
|
Roles = [
|
||||||
|
#{
|
||||||
|
<<"id">> => <<"999">>,
|
||||||
|
<<"permissions">> => integer_to_binary(constants:stream_permission())
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Data0 = maps:get(data, State, #{}),
|
||||||
|
maps:put(data, maps:put(<<"roles">>, Roles, Data0), State).
|
||||||
|
|
||||||
camera_state(SharerCount) ->
|
camera_state(SharerCount) ->
|
||||||
maps:put(
|
maps:put(
|
||||||
voice_states, camera_test_voice_states(SharerCount), camera_base_state(SharerCount)
|
voice_states, camera_test_voice_states(SharerCount), camera_base_state(SharerCount)
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ users_in_channel_test() ->
|
|||||||
?assertNot(sets:is_element(3, Result)).
|
?assertNot(sets:is_element(3, Result)).
|
||||||
|
|
||||||
permission_sync_disconnects_when_view_channel_lost_test() ->
|
permission_sync_disconnects_when_view_channel_lost_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
ConnectOnly = constants:connect_permission(),
|
ConnectOnly = constants:connect_permission(),
|
||||||
{State, UserId, ChannelId, GuildId} = build_perm_sync_state(ConnectOnly),
|
{State, UserId, ChannelId, GuildId} = build_perm_sync_state(ConnectOnly),
|
||||||
ok = guild_voice_permission_sync:sync_user_voice_permissions(UserId, State),
|
ok = guild_voice_permission_sync:sync_user_voice_permissions(UserId, State),
|
||||||
@@ -146,6 +147,7 @@ permission_sync_disconnects_when_view_channel_lost_test() ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
permission_sync_does_not_disconnect_with_full_perms_test() ->
|
permission_sync_does_not_disconnect_with_full_perms_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
FullPerms =
|
FullPerms =
|
||||||
constants:view_channel_permission() bor
|
constants:view_channel_permission() bor
|
||||||
constants:connect_permission() bor
|
constants:connect_permission() bor
|
||||||
@@ -160,6 +162,123 @@ permission_sync_does_not_disconnect_with_full_perms_test() ->
|
|||||||
?assert(false)
|
?assert(false)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
permission_sync_runs_on_role_permission_update_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
|
{State, UserId, ChannelId, GuildId, RoleId} = build_sync_wiring_state(),
|
||||||
|
_ = guild_state:update_state(
|
||||||
|
guild_role_update,
|
||||||
|
#{
|
||||||
|
<<"role">> => #{
|
||||||
|
<<"id">> => integer_to_binary(RoleId),
|
||||||
|
<<"permissions">> => <<"0">>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State
|
||||||
|
),
|
||||||
|
receive
|
||||||
|
{synced, GuildId, ChannelId, UserId, <<"test-conn">>, Perms} ->
|
||||||
|
?assertEqual(false, maps:get(can_speak, Perms)),
|
||||||
|
?assertEqual(false, maps:get(can_stream, Perms))
|
||||||
|
after 200 ->
|
||||||
|
?assert(false)
|
||||||
|
end.
|
||||||
|
|
||||||
|
permission_sync_runs_on_channel_overwrite_update_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
|
{State, UserId, ChannelId, GuildId, RoleId} = build_sync_wiring_state(),
|
||||||
|
_ = guild_state:update_state(
|
||||||
|
channel_update,
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(ChannelId),
|
||||||
|
<<"type">> => 2,
|
||||||
|
<<"permission_overwrites">> => [
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(RoleId),
|
||||||
|
<<"type">> => 0,
|
||||||
|
<<"allow">> => <<"0">>,
|
||||||
|
<<"deny">> => integer_to_binary(constants:stream_permission())
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
State
|
||||||
|
),
|
||||||
|
receive
|
||||||
|
{synced, GuildId, ChannelId, UserId, <<"test-conn">>, Perms} ->
|
||||||
|
?assertEqual(true, maps:get(can_speak, Perms)),
|
||||||
|
?assertEqual(false, maps:get(can_stream, Perms))
|
||||||
|
after 200 ->
|
||||||
|
?assert(false)
|
||||||
|
end.
|
||||||
|
|
||||||
|
permission_sync_runs_on_member_role_removal_test() ->
|
||||||
|
ok = drain_mailbox(),
|
||||||
|
{State, UserId, ChannelId, GuildId, _RoleId} = build_sync_wiring_state(),
|
||||||
|
_ = guild_state:update_state(
|
||||||
|
guild_member_update,
|
||||||
|
#{
|
||||||
|
<<"user">> => #{<<"id">> => integer_to_binary(UserId)},
|
||||||
|
<<"roles">> => []
|
||||||
|
},
|
||||||
|
State
|
||||||
|
),
|
||||||
|
receive
|
||||||
|
{synced, GuildId, ChannelId, UserId, <<"test-conn">>, Perms} ->
|
||||||
|
?assertEqual(false, maps:get(can_speak, Perms)),
|
||||||
|
?assertEqual(false, maps:get(can_stream, Perms))
|
||||||
|
after 200 ->
|
||||||
|
?assert(false)
|
||||||
|
end.
|
||||||
|
|
||||||
|
build_sync_wiring_state() ->
|
||||||
|
Self = self(),
|
||||||
|
TestFun = fun(GId, ChId, UId, ConnId, Perms) ->
|
||||||
|
Self ! {synced, GId, ChId, UId, ConnId, Perms}
|
||||||
|
end,
|
||||||
|
UserId = 10,
|
||||||
|
ChannelId = 500,
|
||||||
|
GuildId = 42,
|
||||||
|
RoleId = 999,
|
||||||
|
RoleIdBin = integer_to_binary(RoleId),
|
||||||
|
EveryonePerms = required_voice_perms(),
|
||||||
|
RolePerms = constants:speak_permission() bor constants:stream_permission(),
|
||||||
|
State = #{
|
||||||
|
id => GuildId,
|
||||||
|
voice_states => #{
|
||||||
|
<<"conn">> => #{
|
||||||
|
<<"user_id">> => integer_to_binary(UserId),
|
||||||
|
<<"channel_id">> => integer_to_binary(ChannelId),
|
||||||
|
<<"connection_id">> => <<"test-conn">>,
|
||||||
|
<<"deaf">> => false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
member_list_subscriptions => guild_member_list_subs:new(),
|
||||||
|
test_permission_sync_fun => TestFun,
|
||||||
|
data => #{
|
||||||
|
<<"guild">> => #{<<"owner_id">> => <<"1">>},
|
||||||
|
<<"roles">> => [
|
||||||
|
#{<<"id">> => RoleIdBin, <<"permissions">> => integer_to_binary(RolePerms)},
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(GuildId),
|
||||||
|
<<"permissions">> => integer_to_binary(EveryonePerms)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
<<"members">> => [
|
||||||
|
#{
|
||||||
|
<<"user">> => #{<<"id">> => integer_to_binary(UserId)},
|
||||||
|
<<"roles">> => [RoleIdBin]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
<<"channels">> => [
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(ChannelId),
|
||||||
|
<<"type">> => 2,
|
||||||
|
<<"permission_overwrites">> => []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{State, UserId, ChannelId, GuildId, RoleId}.
|
||||||
|
|
||||||
build_perm_sync_state(Permissions) ->
|
build_perm_sync_state(Permissions) ->
|
||||||
Self = self(),
|
Self = self(),
|
||||||
TestFun = fun(GId, ChId, UId, ConnId, Perms) ->
|
TestFun = fun(GId, ChId, UId, ConnId, Perms) ->
|
||||||
@@ -221,3 +340,10 @@ camera_limited_voice_states(Count, ChannelId) ->
|
|||||||
#{},
|
#{},
|
||||||
lists:seq(1, Count)
|
lists:seq(1, Count)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
drain_mailbox() ->
|
||||||
|
receive
|
||||||
|
_ -> drain_mailbox()
|
||||||
|
after 0 ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|||||||
@@ -103,6 +103,84 @@ apply_voice_permissions_to_flags_suppresses_without_speak_test() ->
|
|||||||
?assertEqual(false, maps:get(self_mute, Result)),
|
?assertEqual(false, maps:get(self_mute, Result)),
|
||||||
?assertEqual(true, maps:get(suppress, Result)).
|
?assertEqual(true, maps:get(suppress, Result)).
|
||||||
|
|
||||||
|
apply_voice_permissions_to_flags_clears_self_stream_without_stream_test() ->
|
||||||
|
Flags = #{self_stream => true, self_video => true, suppress => false},
|
||||||
|
VoicePerms = #{
|
||||||
|
can_speak => true,
|
||||||
|
can_stream => false,
|
||||||
|
can_video => false
|
||||||
|
},
|
||||||
|
Result = voice_utils:apply_voice_permissions_to_flags(Flags, VoicePerms),
|
||||||
|
?assertEqual(false, maps:get(self_stream, Result)),
|
||||||
|
?assertEqual(false, maps:get(self_video, Result)),
|
||||||
|
?assertEqual(false, maps:get(suppress, Result)).
|
||||||
|
|
||||||
|
apply_voice_permissions_to_flags_keeps_self_stream_with_stream_test() ->
|
||||||
|
Flags = #{self_stream => true, self_video => true, suppress => false},
|
||||||
|
VoicePerms = #{
|
||||||
|
can_speak => true,
|
||||||
|
can_stream => true,
|
||||||
|
can_video => true
|
||||||
|
},
|
||||||
|
Result = voice_utils:apply_voice_permissions_to_flags(Flags, VoicePerms),
|
||||||
|
?assertEqual(true, maps:get(self_stream, Result)),
|
||||||
|
?assertEqual(true, maps:get(self_video, Result)).
|
||||||
|
|
||||||
|
apply_voice_permissions_to_flags_clears_self_stream_denied_by_overwrite_test() ->
|
||||||
|
UserId = 10,
|
||||||
|
ChannelId = 500,
|
||||||
|
State = stream_denied_overwrite_state(UserId, ChannelId),
|
||||||
|
VoicePerms = voice_utils:compute_voice_permissions(UserId, ChannelId, State),
|
||||||
|
?assertEqual(true, maps:get(can_speak, VoicePerms)),
|
||||||
|
?assertEqual(false, maps:get(can_stream, VoicePerms)),
|
||||||
|
Result = voice_utils:apply_voice_permissions_to_flags(
|
||||||
|
#{self_stream => true, self_video => true, suppress => false}, VoicePerms
|
||||||
|
),
|
||||||
|
?assertEqual(false, maps:get(self_stream, Result)),
|
||||||
|
?assertEqual(false, maps:get(self_video, Result)),
|
||||||
|
?assertEqual(false, maps:get(suppress, Result)).
|
||||||
|
|
||||||
|
stream_denied_overwrite_state(UserId, ChannelId) ->
|
||||||
|
GuildId = 90,
|
||||||
|
RoleId = 300,
|
||||||
|
RolePerms =
|
||||||
|
constants:view_channel_permission() bor
|
||||||
|
constants:connect_permission() bor
|
||||||
|
constants:speak_permission() bor
|
||||||
|
constants:stream_permission(),
|
||||||
|
#{
|
||||||
|
id => GuildId,
|
||||||
|
data => #{
|
||||||
|
<<"guild">> => #{<<"owner_id">> => <<"999">>},
|
||||||
|
<<"roles">> => [
|
||||||
|
#{<<"id">> => integer_to_binary(GuildId), <<"permissions">> => <<"0">>},
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(RoleId),
|
||||||
|
<<"permissions">> => integer_to_binary(RolePerms)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
<<"members">> => [
|
||||||
|
#{
|
||||||
|
<<"user">> => #{<<"id">> => integer_to_binary(UserId)},
|
||||||
|
<<"roles">> => [integer_to_binary(RoleId)]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
<<"channels">> => [
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(ChannelId),
|
||||||
|
<<"permission_overwrites">> => [
|
||||||
|
#{
|
||||||
|
<<"id">> => integer_to_binary(RoleId),
|
||||||
|
<<"type">> => 0,
|
||||||
|
<<"allow">> => <<"0">>,
|
||||||
|
<<"deny">> => integer_to_binary(constants:stream_permission())
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
generate_token_nonce_format_test() ->
|
generate_token_nonce_format_test() ->
|
||||||
Nonce = voice_utils:generate_token_nonce(),
|
Nonce = voice_utils:generate_token_nonce(),
|
||||||
?assert(is_binary(Nonce)),
|
?assert(is_binary(Nonce)),
|
||||||
|
|||||||
Reference in New Issue
Block a user