mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
246 lines
9.4 KiB
Erlang
246 lines
9.4 KiB
Erlang
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
-module(guild_availability_check).
|
|
-typing([eqwalizer]).
|
|
|
|
-export([
|
|
get_unavailability_mode_from_state/1,
|
|
is_unavailable_hidden_enabled/1,
|
|
is_unavailable_hidden_enabled_from_features/1,
|
|
is_user_staff/2,
|
|
is_user_staff_from_user_data/1,
|
|
check_unavailability_transition/2
|
|
]).
|
|
|
|
-type guild_state() :: map().
|
|
-type user_id() :: integer().
|
|
-type unavailability_mode() ::
|
|
available
|
|
| unavailable_for_everyone
|
|
| unavailable_for_everyone_but_staff.
|
|
-type transition_result() ::
|
|
{unavailable_enabled, boolean()} | unavailable_disabled | no_change.
|
|
|
|
-export_type([guild_state/0, user_id/0, unavailability_mode/0, transition_result/0]).
|
|
|
|
-ifdef(TEST).
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-endif.
|
|
|
|
-spec get_unavailability_mode_from_state(guild_state()) -> unavailability_mode().
|
|
get_unavailability_mode_from_state(State) ->
|
|
Data = maps:get(data, State, #{}),
|
|
Guild = maps:get(<<"guild">>, Data, #{}),
|
|
Features = maps:get(<<"features">>, Guild, []),
|
|
get_unavailability_mode_from_features(Features).
|
|
|
|
-spec get_unavailability_mode_from_features(term()) -> unavailability_mode().
|
|
get_unavailability_mode_from_features(Features) when is_list(Features) ->
|
|
HasForEveryone = lists:member(<<"UNAVAILABLE_FOR_EVERYONE">>, Features),
|
|
HasForEveryoneButStaff =
|
|
lists:member(<<"UNAVAILABLE_FOR_EVERYONE_BUT_STAFF">>, Features),
|
|
case {HasForEveryone, HasForEveryoneButStaff} of
|
|
{true, _} -> unavailable_for_everyone;
|
|
{false, true} -> unavailable_for_everyone_but_staff;
|
|
{false, false} -> available
|
|
end;
|
|
get_unavailability_mode_from_features(_) ->
|
|
available.
|
|
|
|
-spec is_unavailable_hidden_enabled(guild_state()) -> boolean().
|
|
is_unavailable_hidden_enabled(State) ->
|
|
Data = maps:get(data, State, #{}),
|
|
Guild = maps:get(<<"guild">>, Data, #{}),
|
|
Features = maps:get(<<"features">>, Guild, []),
|
|
is_unavailable_hidden_enabled_from_features(Features).
|
|
|
|
-spec is_unavailable_hidden_enabled_from_features(term()) -> boolean().
|
|
is_unavailable_hidden_enabled_from_features(Features) when is_list(Features) ->
|
|
HasUnavailableHidden = lists:member(<<"UNAVAILABLE_HIDDEN">>, Features),
|
|
IsUnavailable = get_unavailability_mode_from_features(Features) =/= available,
|
|
HasUnavailableHidden andalso IsUnavailable;
|
|
is_unavailable_hidden_enabled_from_features(_) ->
|
|
false.
|
|
|
|
-spec is_user_staff(user_id(), guild_state()) -> boolean().
|
|
is_user_staff(UserId, State) ->
|
|
case is_user_staff_from_sessions(UserId, State) of
|
|
true -> true;
|
|
false -> false;
|
|
undefined -> is_user_staff_from_member(UserId, State)
|
|
end.
|
|
|
|
-spec is_user_staff_from_sessions(user_id(), guild_state()) -> boolean() | undefined.
|
|
is_user_staff_from_sessions(UserId, State) ->
|
|
Sessions = maps:get(sessions, State, #{}),
|
|
maps:fold(
|
|
fun(_SessionId, SessionData, Acc) ->
|
|
fold_check_staff(UserId, SessionData, Acc)
|
|
end,
|
|
undefined,
|
|
Sessions
|
|
).
|
|
|
|
-spec fold_check_staff(user_id(), map(), boolean() | undefined) -> boolean() | undefined.
|
|
fold_check_staff(_UserId, _SessionData, Acc) when Acc =/= undefined ->
|
|
Acc;
|
|
fold_check_staff(UserId, SessionData, undefined) ->
|
|
check_session_staff(UserId, SessionData).
|
|
|
|
-spec check_session_staff(user_id(), map()) -> boolean() | undefined.
|
|
check_session_staff(UserId, SessionData) ->
|
|
SessionUserId = maps:get(user_id, SessionData, undefined),
|
|
SessionIsStaff = maps:get(is_staff, SessionData, undefined),
|
|
case {SessionUserId =:= UserId, SessionIsStaff} of
|
|
{true, true} -> true;
|
|
{true, false} -> false;
|
|
_ -> undefined
|
|
end.
|
|
|
|
-spec is_user_staff_from_member(user_id(), guild_state()) -> boolean().
|
|
is_user_staff_from_member(UserId, State) ->
|
|
case guild_permissions:find_member_by_user_id(UserId, State) of
|
|
undefined ->
|
|
false;
|
|
Member ->
|
|
User = maps:get(<<"user">>, Member, #{}),
|
|
is_user_staff_from_user_data(User)
|
|
end.
|
|
|
|
-spec is_user_staff_from_user_data(map()) -> boolean().
|
|
is_user_staff_from_user_data(UserData) when is_map(UserData) ->
|
|
case parse_is_staff_value(maps:get(<<"is_staff">>, UserData, undefined)) of
|
|
undefined -> is_user_staff_from_flags(UserData);
|
|
IsStaff -> IsStaff
|
|
end;
|
|
is_user_staff_from_user_data(_) ->
|
|
false.
|
|
|
|
-spec parse_is_staff_value(term()) -> boolean() | undefined.
|
|
parse_is_staff_value(true) -> true;
|
|
parse_is_staff_value(false) -> false;
|
|
parse_is_staff_value(<<"true">>) -> true;
|
|
parse_is_staff_value(<<"false">>) -> false;
|
|
parse_is_staff_value(_) -> undefined.
|
|
|
|
-spec is_user_staff_from_flags(map()) -> boolean().
|
|
is_user_staff_from_flags(UserData) ->
|
|
user_flags:is_staff(maps:get(<<"flags">>, UserData, 0)).
|
|
|
|
-spec check_unavailability_transition(guild_state(), guild_state()) -> transition_result().
|
|
check_unavailability_transition(OldState, NewState) ->
|
|
OldMode = get_unavailability_mode_from_state(OldState),
|
|
NewMode = get_unavailability_mode_from_state(NewState),
|
|
transition_result(OldMode, NewMode).
|
|
|
|
-spec transition_result(unavailability_mode(), unavailability_mode()) -> transition_result().
|
|
transition_result(available, unavailable_for_everyone) ->
|
|
{unavailable_enabled, false};
|
|
transition_result(available, unavailable_for_everyone_but_staff) ->
|
|
{unavailable_enabled, true};
|
|
transition_result(unavailable_for_everyone, available) ->
|
|
unavailable_disabled;
|
|
transition_result(unavailable_for_everyone_but_staff, available) ->
|
|
unavailable_disabled;
|
|
transition_result(unavailable_for_everyone_but_staff, unavailable_for_everyone) ->
|
|
{unavailable_enabled, false};
|
|
transition_result(unavailable_for_everyone, unavailable_for_everyone_but_staff) ->
|
|
{unavailable_enabled, true};
|
|
transition_result(_, _) ->
|
|
no_change.
|
|
|
|
-ifdef(TEST).
|
|
|
|
get_unavailability_mode_available_test() ->
|
|
State = #{data => #{<<"guild">> => #{<<"features">> => []}}},
|
|
?assertEqual(available, get_unavailability_mode_from_state(State)).
|
|
|
|
get_unavailability_mode_for_everyone_test() ->
|
|
State = #{data => #{<<"guild">> => #{<<"features">> => [<<"UNAVAILABLE_FOR_EVERYONE">>]}}},
|
|
?assertEqual(unavailable_for_everyone, get_unavailability_mode_from_state(State)).
|
|
|
|
get_unavailability_mode_for_everyone_but_staff_test() ->
|
|
Features = [<<"UNAVAILABLE_FOR_EVERYONE_BUT_STAFF">>],
|
|
State = #{data => #{<<"guild">> => #{<<"features">> => Features}}},
|
|
?assertEqual(
|
|
unavailable_for_everyone_but_staff,
|
|
get_unavailability_mode_from_state(State)
|
|
).
|
|
|
|
is_unavailable_hidden_enabled_test() ->
|
|
HiddenFeatures = [<<"UNAVAILABLE_FOR_EVERYONE">>, <<"UNAVAILABLE_HIDDEN">>],
|
|
StateHidden = #{data => #{<<"guild">> => #{<<"features">> => HiddenFeatures}}},
|
|
NotHiddenFeatures = [<<"UNAVAILABLE_HIDDEN">>],
|
|
StateNotHidden = #{data => #{<<"guild">> => #{<<"features">> => NotHiddenFeatures}}},
|
|
?assertEqual(true, is_unavailable_hidden_enabled(StateHidden)),
|
|
?assertEqual(false, is_unavailable_hidden_enabled(StateNotHidden)).
|
|
|
|
check_unavailability_transition_no_change_test() ->
|
|
State = #{data => #{<<"guild">> => #{<<"features">> => []}}},
|
|
?assertEqual(no_change, check_unavailability_transition(State, State)).
|
|
|
|
check_unavailability_transition_enabled_test() ->
|
|
Old = #{data => #{<<"guild">> => #{<<"features">> => []}}},
|
|
New = #{data => #{<<"guild">> => #{<<"features">> => [<<"UNAVAILABLE_FOR_EVERYONE">>]}}},
|
|
?assertEqual({unavailable_enabled, false}, check_unavailability_transition(Old, New)).
|
|
|
|
check_unavailability_transition_disabled_test() ->
|
|
Old = #{data => #{<<"guild">> => #{<<"features">> => [<<"UNAVAILABLE_FOR_EVERYONE">>]}}},
|
|
New = #{data => #{<<"guild">> => #{<<"features">> => []}}},
|
|
?assertEqual(unavailable_disabled, check_unavailability_transition(Old, New)).
|
|
|
|
is_user_staff_from_user_data_test() ->
|
|
?assertEqual(true, is_user_staff_from_user_data(#{<<"is_staff">> => true})),
|
|
?assertEqual(false, is_user_staff_from_user_data(#{<<"is_staff">> => false})),
|
|
?assertEqual(true, is_user_staff_from_user_data(#{<<"flags">> => 1})),
|
|
?assertEqual(false, is_user_staff_from_user_data(#{<<"flags">> => 0})),
|
|
?assertEqual(false, is_user_staff_from_user_data(#{})).
|
|
|
|
parse_is_staff_value_test() ->
|
|
?assertEqual(true, parse_is_staff_value(true)),
|
|
?assertEqual(false, parse_is_staff_value(false)),
|
|
?assertEqual(true, parse_is_staff_value(<<"true">>)),
|
|
?assertEqual(false, parse_is_staff_value(<<"false">>)),
|
|
?assertEqual(undefined, parse_is_staff_value(something)).
|
|
|
|
is_user_staff_ignores_injected_member_data_test() ->
|
|
UserId = 42,
|
|
Sessions = #{
|
|
<<"sess1">> => #{
|
|
session_id => <<"sess1">>,
|
|
user_id => UserId,
|
|
is_staff => false
|
|
}
|
|
},
|
|
InjectedMember = #{
|
|
<<"user">> => #{<<"id">> => <<"42">>, <<"is_staff">> => true, <<"flags">> => <<"0">>}
|
|
},
|
|
State = #{
|
|
sessions => Sessions,
|
|
data => #{
|
|
<<"members">> => [InjectedMember],
|
|
<<"guild">> => #{<<"features">> => []}
|
|
}
|
|
},
|
|
?assertEqual(false, is_user_staff(UserId, State)).
|
|
|
|
is_user_staff_trusts_session_identity_test() ->
|
|
UserId = 99,
|
|
Sessions = #{
|
|
<<"sess2">> => #{
|
|
session_id => <<"sess2">>,
|
|
user_id => UserId,
|
|
is_staff => true
|
|
}
|
|
},
|
|
State = #{
|
|
sessions => Sessions,
|
|
data => #{
|
|
<<"members">> => [],
|
|
<<"guild">> => #{<<"features">> => []}
|
|
}
|
|
},
|
|
?assertEqual(true, is_user_staff(UserId, State)).
|
|
|
|
-endif.
|