mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +01:00
Support MUC hats (XEP-0317, conversejs/prosody compatible)
This commit is contained in:
parent
5462a26a0a
commit
5d0e599f17
@ -65,6 +65,7 @@
|
|||||||
captcha_whitelist = (?SETS):empty() :: gb_sets:set(),
|
captcha_whitelist = (?SETS):empty() :: gb_sets:set(),
|
||||||
mam = false :: boolean(),
|
mam = false :: boolean(),
|
||||||
pubsub = <<"">> :: binary(),
|
pubsub = <<"">> :: binary(),
|
||||||
|
enable_hats = false :: boolean(),
|
||||||
lang = ejabberd_option:language() :: binary()
|
lang = ejabberd_option:language() :: binary()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@ -124,6 +125,7 @@
|
|||||||
history = #lqueue{} :: lqueue(),
|
history = #lqueue{} :: lqueue(),
|
||||||
subject = [] :: [text()],
|
subject = [] :: [text()],
|
||||||
subject_author = <<"">> :: binary(),
|
subject_author = <<"">> :: binary(),
|
||||||
|
hats_users = #{} :: #{ljid() => #{binary() => binary()}},
|
||||||
just_created = erlang:system_time(microsecond) :: true | integer(),
|
just_created = erlang:system_time(microsecond) :: true | integer(),
|
||||||
activity = treap:empty() :: treap:treap(),
|
activity = treap:empty() :: treap:treap(),
|
||||||
room_shaper = none :: ejabberd_shaper:shaper(),
|
room_shaper = none :: ejabberd_shaper:shaper(),
|
||||||
|
@ -76,6 +76,12 @@
|
|||||||
|
|
||||||
-define(DEFAULT_MAX_USERS_PRESENCE,1000).
|
-define(DEFAULT_MAX_USERS_PRESENCE,1000).
|
||||||
|
|
||||||
|
-define(MUC_HAT_ADD_CMD, <<"http://prosody.im/protocol/hats#add">>).
|
||||||
|
-define(MUC_HAT_REMOVE_CMD, <<"http://prosody.im/protocol/hats#remove">>).
|
||||||
|
-define(MUC_HAT_LIST_CMD, <<"p1:hats#list">>).
|
||||||
|
-define(MAX_HATS_USERS, 100).
|
||||||
|
-define(MAX_HATS_PER_USER, 10).
|
||||||
|
|
||||||
%-define(DBGFSM, true).
|
%-define(DBGFSM, true).
|
||||||
|
|
||||||
-ifdef(DBGFSM).
|
-ifdef(DBGFSM).
|
||||||
@ -446,6 +452,8 @@ normal_state({route, <<"">>,
|
|||||||
process_iq_mucsub(From, IQ, StateData);
|
process_iq_mucsub(From, IQ, StateData);
|
||||||
#xcaptcha{} ->
|
#xcaptcha{} ->
|
||||||
process_iq_captcha(From, IQ, StateData);
|
process_iq_captcha(From, IQ, StateData);
|
||||||
|
#adhoc_command{} ->
|
||||||
|
process_iq_adhoc(From, IQ, StateData);
|
||||||
_ ->
|
_ ->
|
||||||
Txt = ?T("The feature requested is not "
|
Txt = ?T("The feature requested is not "
|
||||||
"supported by the conference"),
|
"supported by the conference"),
|
||||||
@ -1405,6 +1413,12 @@ is_occupant_or_admin(JID, StateData) ->
|
|||||||
_ -> false
|
_ -> false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% Check if the user is an admin or owner.
|
||||||
|
-spec is_admin(jid(), state()) -> boolean().
|
||||||
|
is_admin(JID, StateData) ->
|
||||||
|
FAffiliation = get_affiliation(JID, StateData),
|
||||||
|
FAffiliation == admin orelse FAffiliation == owner.
|
||||||
|
|
||||||
%% Decide the fate of the message and its sender
|
%% Decide the fate of the message and its sender
|
||||||
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
|
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
|
||||||
-spec decide_fate_message(message(), jid(), state()) ->
|
-spec decide_fate_message(message(), jid(), state()) ->
|
||||||
@ -1935,7 +1949,7 @@ filter_presence(Presence) ->
|
|||||||
XMLNS = xmpp:get_ns(El),
|
XMLNS = xmpp:get_ns(El),
|
||||||
case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
|
case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
|
||||||
?NS_MUC -> false;
|
?NS_MUC -> false;
|
||||||
_ -> true
|
_ -> XMLNS /= ?NS_HATS
|
||||||
end
|
end
|
||||||
end, xmpp:get_els(Presence)),
|
end, xmpp:get_els(Presence)),
|
||||||
xmpp:set_els(Presence, Els).
|
xmpp:set_els(Presence, Els).
|
||||||
@ -2485,9 +2499,10 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
|
|||||||
Pres = if Presence == undefined -> #presence{};
|
Pres = if Presence == undefined -> #presence{};
|
||||||
true -> Presence
|
true -> Presence
|
||||||
end,
|
end,
|
||||||
Packet = xmpp:set_subtag(
|
Packet = xmpp:set_subtag(
|
||||||
Pres, #muc_user{items = [Item],
|
add_presence_hats(NJID, Pres, StateData),
|
||||||
status_codes = StatusCodes}),
|
#muc_user{items = [Item],
|
||||||
|
status_codes = StatusCodes}),
|
||||||
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
|
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
|
||||||
Info#user.jid, Packet, Node1, StateData),
|
Info#user.jid, Packet, Node1, StateData),
|
||||||
Type = xmpp:get_type(Packet),
|
Type = xmpp:get_type(Packet),
|
||||||
@ -2536,7 +2551,9 @@ send_existing_presences1(ToJID, StateData) ->
|
|||||||
false -> Item0
|
false -> Item0
|
||||||
end,
|
end,
|
||||||
Packet = xmpp:set_subtag(
|
Packet = xmpp:set_subtag(
|
||||||
Presence, #muc_user{items = [Item]}),
|
add_presence_hats(
|
||||||
|
FromJID, Presence, StateData),
|
||||||
|
#muc_user{items = [Item]}),
|
||||||
send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
|
send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
|
||||||
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
|
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
|
||||||
end
|
end
|
||||||
@ -3579,7 +3596,8 @@ get_config(Lang, StateData, From) ->
|
|||||||
{allow_voice_requests, Config#config.allow_voice_requests},
|
{allow_voice_requests, Config#config.allow_voice_requests},
|
||||||
{allow_subscription, Config#config.allow_subscription},
|
{allow_subscription, Config#config.allow_subscription},
|
||||||
{voice_request_min_interval, Config#config.voice_request_min_interval},
|
{voice_request_min_interval, Config#config.voice_request_min_interval},
|
||||||
{pubsub, Config#config.pubsub}]
|
{pubsub, Config#config.pubsub},
|
||||||
|
{enable_hats, Config#config.enable_hats}]
|
||||||
++
|
++
|
||||||
case ejabberd_captcha:is_feature_available() of
|
case ejabberd_captcha:is_feature_available() of
|
||||||
true ->
|
true ->
|
||||||
@ -3667,6 +3685,7 @@ set_config(Opts, Config, ServerHost, Lang) ->
|
|||||||
({maxusers, V}, C) -> C#config{max_users = V};
|
({maxusers, V}, C) -> C#config{max_users = V};
|
||||||
({enablelogging, V}, C) -> C#config{logging = V};
|
({enablelogging, V}, C) -> C#config{logging = V};
|
||||||
({pubsub, V}, C) -> C#config{pubsub = V};
|
({pubsub, V}, C) -> C#config{pubsub = V};
|
||||||
|
({enable_hats, V}, C) -> C#config{enable_hats = V};
|
||||||
({lang, L}, C) -> C#config{lang = L};
|
({lang, L}, C) -> C#config{lang = L};
|
||||||
({captcha_whitelist, Js}, C) ->
|
({captcha_whitelist, Js}, C) ->
|
||||||
LJIDs = [jid:tolower(J) || J <- Js],
|
LJIDs = [jid:tolower(J) || J <- Js],
|
||||||
@ -3897,6 +3916,9 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
|||||||
allow_subscription ->
|
allow_subscription ->
|
||||||
StateData#state{config =
|
StateData#state{config =
|
||||||
(StateData#state.config)#config{allow_subscription = Val}};
|
(StateData#state.config)#config{allow_subscription = Val}};
|
||||||
|
enable_hats ->
|
||||||
|
StateData#state{config =
|
||||||
|
(StateData#state.config)#config{enable_hats = Val}};
|
||||||
lang ->
|
lang ->
|
||||||
StateData#state{config =
|
StateData#state{config =
|
||||||
(StateData#state.config)#config{lang = Val}};
|
(StateData#state.config)#config{lang = Val}};
|
||||||
@ -3927,6 +3949,11 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
|||||||
end,
|
end,
|
||||||
StateData#state{subject = Subj};
|
StateData#state{subject = Subj};
|
||||||
subject_author -> StateData#state{subject_author = Val};
|
subject_author -> StateData#state{subject_author = Val};
|
||||||
|
hats_users ->
|
||||||
|
Hats = maps:from_list(
|
||||||
|
lists:map(fun({U, H}) -> {U, maps:from_list(H)} end,
|
||||||
|
Val)),
|
||||||
|
StateData#state{hats_users = Hats};
|
||||||
_ -> StateData
|
_ -> StateData
|
||||||
end,
|
end,
|
||||||
set_opts(Opts, NSD).
|
set_opts(Opts, NSD).
|
||||||
@ -3983,6 +4010,7 @@ make_opts(StateData) ->
|
|||||||
?MAKE_CONFIG_OPT(#config.vcard),
|
?MAKE_CONFIG_OPT(#config.vcard),
|
||||||
?MAKE_CONFIG_OPT(#config.vcard_xupdate),
|
?MAKE_CONFIG_OPT(#config.vcard_xupdate),
|
||||||
?MAKE_CONFIG_OPT(#config.pubsub),
|
?MAKE_CONFIG_OPT(#config.pubsub),
|
||||||
|
?MAKE_CONFIG_OPT(#config.enable_hats),
|
||||||
?MAKE_CONFIG_OPT(#config.lang),
|
?MAKE_CONFIG_OPT(#config.lang),
|
||||||
{captcha_whitelist,
|
{captcha_whitelist,
|
||||||
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
|
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
|
||||||
@ -3990,6 +4018,9 @@ make_opts(StateData) ->
|
|||||||
maps:to_list(StateData#state.affiliations)},
|
maps:to_list(StateData#state.affiliations)},
|
||||||
{subject, StateData#state.subject},
|
{subject, StateData#state.subject},
|
||||||
{subject_author, StateData#state.subject_author},
|
{subject_author, StateData#state.subject_author},
|
||||||
|
{hats_users,
|
||||||
|
lists:map(fun({U, H}) -> {U, maps:to_list(H)} end,
|
||||||
|
maps:to_list(StateData#state.hats_users))},
|
||||||
{hibernation_time, erlang:system_time(microsecond)},
|
{hibernation_time, erlang:system_time(microsecond)},
|
||||||
{subscribers, Subscribers}].
|
{subscribers, Subscribers}].
|
||||||
|
|
||||||
@ -4080,6 +4111,7 @@ maybe_forget_room(StateData) ->
|
|||||||
make_disco_info(_From, StateData) ->
|
make_disco_info(_From, StateData) ->
|
||||||
Config = StateData#state.config,
|
Config = StateData#state.config,
|
||||||
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||||
|
?NS_COMMANDS,
|
||||||
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
||||||
<<"muc_public">>, <<"muc_hidden">>),
|
<<"muc_public">>, <<"muc_hidden">>),
|
||||||
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
|
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
|
||||||
@ -4119,6 +4151,77 @@ process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
|||||||
DiscoInfo = make_disco_info(From, StateData),
|
DiscoInfo = make_disco_info(From, StateData),
|
||||||
Extras = iq_disco_info_extras(Lang, StateData, false),
|
Extras = iq_disco_info_extras(Lang, StateData, false),
|
||||||
{result, DiscoInfo#disco_info{xdata = [Extras]}};
|
{result, DiscoInfo#disco_info{xdata = [Extras]}};
|
||||||
|
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||||
|
sub_els = [#disco_info{node = ?NS_COMMANDS}]},
|
||||||
|
StateData) ->
|
||||||
|
case (StateData#state.config)#config.enable_hats andalso
|
||||||
|
is_admin(From, StateData)
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{result,
|
||||||
|
#disco_info{
|
||||||
|
identities = [#identity{category = <<"automation">>,
|
||||||
|
type = <<"command-list">>,
|
||||||
|
name = translate:translate(
|
||||||
|
Lang, ?T("Commands"))}]}};
|
||||||
|
false ->
|
||||||
|
Txt = ?T("Node not found"),
|
||||||
|
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||||
|
end;
|
||||||
|
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||||
|
sub_els = [#disco_info{node = ?MUC_HAT_ADD_CMD}]},
|
||||||
|
StateData) ->
|
||||||
|
case (StateData#state.config)#config.enable_hats andalso
|
||||||
|
is_admin(From, StateData)
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{result,
|
||||||
|
#disco_info{
|
||||||
|
identities = [#identity{category = <<"automation">>,
|
||||||
|
type = <<"command-node">>,
|
||||||
|
name = translate:translate(
|
||||||
|
Lang, ?T("Add a hat to a user"))}],
|
||||||
|
features = [?NS_COMMANDS]}};
|
||||||
|
false ->
|
||||||
|
Txt = ?T("Node not found"),
|
||||||
|
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||||
|
end;
|
||||||
|
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||||
|
sub_els = [#disco_info{node = ?MUC_HAT_REMOVE_CMD}]},
|
||||||
|
StateData) ->
|
||||||
|
case (StateData#state.config)#config.enable_hats andalso
|
||||||
|
is_admin(From, StateData)
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{result,
|
||||||
|
#disco_info{
|
||||||
|
identities = [#identity{category = <<"automation">>,
|
||||||
|
type = <<"command-node">>,
|
||||||
|
name = translate:translate(
|
||||||
|
Lang, ?T("Remove a hat from a user"))}],
|
||||||
|
features = [?NS_COMMANDS]}};
|
||||||
|
false ->
|
||||||
|
Txt = ?T("Node not found"),
|
||||||
|
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||||
|
end;
|
||||||
|
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||||
|
sub_els = [#disco_info{node = ?MUC_HAT_LIST_CMD}]},
|
||||||
|
StateData) ->
|
||||||
|
case (StateData#state.config)#config.enable_hats andalso
|
||||||
|
is_admin(From, StateData)
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{result,
|
||||||
|
#disco_info{
|
||||||
|
identities = [#identity{category = <<"automation">>,
|
||||||
|
type = <<"command-node">>,
|
||||||
|
name = translate:translate(
|
||||||
|
Lang, ?T("List users with hats"))}],
|
||||||
|
features = [?NS_COMMANDS]}};
|
||||||
|
false ->
|
||||||
|
Txt = ?T("Node not found"),
|
||||||
|
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||||
|
end;
|
||||||
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||||
sub_els = [#disco_info{node = Node}]},
|
sub_els = [#disco_info{node = Node}]},
|
||||||
StateData) ->
|
StateData) ->
|
||||||
@ -4199,6 +4302,46 @@ process_iq_disco_items(From, #iq{type = get, sub_els = [#disco_items{node = <<>>
|
|||||||
{result, #disco_items{}}
|
{result, #disco_items{}}
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
|
process_iq_disco_items(From, #iq{type = get, lang = Lang,
|
||||||
|
sub_els = [#disco_items{node = ?NS_COMMANDS}]},
|
||||||
|
StateData) ->
|
||||||
|
case (StateData#state.config)#config.enable_hats andalso
|
||||||
|
is_admin(From, StateData)
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{result,
|
||||||
|
#disco_items{
|
||||||
|
items = [#disco_item{jid = StateData#state.jid,
|
||||||
|
node = ?MUC_HAT_ADD_CMD,
|
||||||
|
name = translate:translate(
|
||||||
|
Lang, ?T("Add a hat to a user"))},
|
||||||
|
#disco_item{jid = StateData#state.jid,
|
||||||
|
node = ?MUC_HAT_REMOVE_CMD,
|
||||||
|
name = translate:translate(
|
||||||
|
Lang, ?T("Remove a hat from a user"))},
|
||||||
|
#disco_item{jid = StateData#state.jid,
|
||||||
|
node = ?MUC_HAT_LIST_CMD,
|
||||||
|
name = translate:translate(
|
||||||
|
Lang, ?T("List users with hats"))}]}};
|
||||||
|
false ->
|
||||||
|
Txt = ?T("Node not found"),
|
||||||
|
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||||
|
end;
|
||||||
|
process_iq_disco_items(From, #iq{type = get, lang = Lang,
|
||||||
|
sub_els = [#disco_items{node = Node}]},
|
||||||
|
StateData)
|
||||||
|
when Node == ?MUC_HAT_ADD_CMD;
|
||||||
|
Node == ?MUC_HAT_REMOVE_CMD;
|
||||||
|
Node == ?MUC_HAT_LIST_CMD ->
|
||||||
|
case (StateData#state.config)#config.enable_hats andalso
|
||||||
|
is_admin(From, StateData)
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{result, #disco_items{}};
|
||||||
|
false ->
|
||||||
|
Txt = ?T("Node not found"),
|
||||||
|
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||||
|
end;
|
||||||
process_iq_disco_items(_From, #iq{lang = Lang}, _StateData) ->
|
process_iq_disco_items(_From, #iq{lang = Lang}, _StateData) ->
|
||||||
Txt = ?T("Node not found"),
|
Txt = ?T("Node not found"),
|
||||||
{error, xmpp:err_item_not_found(Txt, Lang)}.
|
{error, xmpp:err_item_not_found(Txt, Lang)}.
|
||||||
@ -4441,6 +4584,271 @@ get_mucroom_disco_items(StateData) ->
|
|||||||
end, [], StateData#state.nicks),
|
end, [], StateData#state.nicks),
|
||||||
#disco_items{items = Items}.
|
#disco_items{items = Items}.
|
||||||
|
|
||||||
|
-spec process_iq_adhoc(jid(), iq(), state()) ->
|
||||||
|
{result, adhoc_command()} |
|
||||||
|
{result, adhoc_command(), state()} |
|
||||||
|
{error, stanza_error()}.
|
||||||
|
process_iq_adhoc(_From, #iq{type = get}, _StateData) ->
|
||||||
|
{error, xmpp:err_bad_request()};
|
||||||
|
process_iq_adhoc(From, #iq{type = set, lang = Lang1,
|
||||||
|
sub_els = [#adhoc_command{} = Request]},
|
||||||
|
StateData) ->
|
||||||
|
% Ad-Hoc Commands are used only for Hats here
|
||||||
|
case (StateData#state.config)#config.enable_hats andalso
|
||||||
|
is_admin(From, StateData)
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
#adhoc_command{lang = Lang2, node = Node,
|
||||||
|
action = Action, xdata = XData} = Request,
|
||||||
|
Lang = case Lang2 of
|
||||||
|
<<"">> -> Lang1;
|
||||||
|
_ -> Lang2
|
||||||
|
end,
|
||||||
|
case {Node, Action} of
|
||||||
|
{_, cancel} ->
|
||||||
|
{result,
|
||||||
|
xmpp_util:make_adhoc_response(
|
||||||
|
Request,
|
||||||
|
#adhoc_command{status = canceled, lang = Lang,
|
||||||
|
node = Node})};
|
||||||
|
{?MUC_HAT_ADD_CMD, execute} ->
|
||||||
|
Form =
|
||||||
|
#xdata{
|
||||||
|
title = translate:translate(
|
||||||
|
Lang, ?T("Add a hat to a user")),
|
||||||
|
type = form,
|
||||||
|
fields =
|
||||||
|
[#xdata_field{
|
||||||
|
type = 'jid-single',
|
||||||
|
label = translate:translate(Lang, ?T("Jabber ID")),
|
||||||
|
required = true,
|
||||||
|
var = <<"jid">>},
|
||||||
|
#xdata_field{
|
||||||
|
type = 'text-single',
|
||||||
|
label = translate:translate(Lang, ?T("Hat title")),
|
||||||
|
var = <<"hat_title">>},
|
||||||
|
#xdata_field{
|
||||||
|
type = 'text-single',
|
||||||
|
label = translate:translate(Lang, ?T("Hat URI")),
|
||||||
|
required = true,
|
||||||
|
var = <<"hat_uri">>}
|
||||||
|
]},
|
||||||
|
{result,
|
||||||
|
xmpp_util:make_adhoc_response(
|
||||||
|
Request,
|
||||||
|
#adhoc_command{
|
||||||
|
status = executing,
|
||||||
|
xdata = Form})};
|
||||||
|
{?MUC_HAT_ADD_CMD, complete} when XData /= undefined ->
|
||||||
|
JID = try
|
||||||
|
jid:decode(hd(xmpp_util:get_xdata_values(
|
||||||
|
<<"jid">>, XData)))
|
||||||
|
catch _:_ -> error
|
||||||
|
end,
|
||||||
|
URI = try
|
||||||
|
hd(xmpp_util:get_xdata_values(
|
||||||
|
<<"hat_uri">>, XData))
|
||||||
|
catch _:_ -> error
|
||||||
|
end,
|
||||||
|
Title = case xmpp_util:get_xdata_values(
|
||||||
|
<<"hat_title">>, XData) of
|
||||||
|
[] -> <<"">>;
|
||||||
|
[T] -> T
|
||||||
|
end,
|
||||||
|
if
|
||||||
|
(JID /= error) and (URI /= error) ->
|
||||||
|
case add_hat(JID, URI, Title, StateData) of
|
||||||
|
{ok, NewStateData} ->
|
||||||
|
store_room(NewStateData),
|
||||||
|
send_update_presence(
|
||||||
|
JID, NewStateData, StateData),
|
||||||
|
{result,
|
||||||
|
xmpp_util:make_adhoc_response(
|
||||||
|
Request,
|
||||||
|
#adhoc_command{status = completed}),
|
||||||
|
NewStateData};
|
||||||
|
{error, size_limit} ->
|
||||||
|
Txt = ?T("Hats limit exceeded"),
|
||||||
|
{error, xmpp:err_not_allowed(Txt, Lang)}
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
{error, xmpp:err_bad_request()}
|
||||||
|
end;
|
||||||
|
{?MUC_HAT_ADD_CMD, complete} ->
|
||||||
|
{error, xmpp:err_bad_request()};
|
||||||
|
{?MUC_HAT_ADD_CMD, _} ->
|
||||||
|
Txt = ?T("Incorrect value of 'action' attribute"),
|
||||||
|
{error, xmpp:err_bad_request(Txt, Lang)};
|
||||||
|
{?MUC_HAT_REMOVE_CMD, execute} ->
|
||||||
|
Form =
|
||||||
|
#xdata{
|
||||||
|
title = translate:translate(
|
||||||
|
Lang, ?T("Remove a hat from a user")),
|
||||||
|
type = form,
|
||||||
|
fields =
|
||||||
|
[#xdata_field{
|
||||||
|
type = 'jid-single',
|
||||||
|
label = translate:translate(Lang, ?T("Jabber ID")),
|
||||||
|
required = true,
|
||||||
|
var = <<"jid">>},
|
||||||
|
#xdata_field{
|
||||||
|
type = 'text-single',
|
||||||
|
label = translate:translate(Lang, ?T("Hat URI")),
|
||||||
|
required = true,
|
||||||
|
var = <<"hat_uri">>}
|
||||||
|
]},
|
||||||
|
{result,
|
||||||
|
xmpp_util:make_adhoc_response(
|
||||||
|
Request,
|
||||||
|
#adhoc_command{
|
||||||
|
status = executing,
|
||||||
|
xdata = Form})};
|
||||||
|
{?MUC_HAT_REMOVE_CMD, complete} when XData /= undefined ->
|
||||||
|
JID = try
|
||||||
|
jid:decode(hd(xmpp_util:get_xdata_values(
|
||||||
|
<<"jid">>, XData)))
|
||||||
|
catch _:_ -> error
|
||||||
|
end,
|
||||||
|
URI = try
|
||||||
|
hd(xmpp_util:get_xdata_values(
|
||||||
|
<<"hat_uri">>, XData))
|
||||||
|
catch _:_ -> error
|
||||||
|
end,
|
||||||
|
if
|
||||||
|
(JID /= error) and (URI /= error) ->
|
||||||
|
NewStateData = del_hat(JID, URI, StateData),
|
||||||
|
store_room(NewStateData),
|
||||||
|
send_update_presence(
|
||||||
|
JID, NewStateData, StateData),
|
||||||
|
{result,
|
||||||
|
xmpp_util:make_adhoc_response(
|
||||||
|
Request,
|
||||||
|
#adhoc_command{status = completed}),
|
||||||
|
NewStateData};
|
||||||
|
true ->
|
||||||
|
{error, xmpp:err_bad_request()}
|
||||||
|
end;
|
||||||
|
{?MUC_HAT_REMOVE_CMD, complete} ->
|
||||||
|
{error, xmpp:err_bad_request()};
|
||||||
|
{?MUC_HAT_REMOVE_CMD, _} ->
|
||||||
|
Txt = ?T("Incorrect value of 'action' attribute"),
|
||||||
|
{error, xmpp:err_bad_request(Txt, Lang)};
|
||||||
|
{?MUC_HAT_LIST_CMD, execute} ->
|
||||||
|
Hats = get_all_hats(StateData),
|
||||||
|
Items =
|
||||||
|
lists:map(
|
||||||
|
fun({JID, URI, Title}) ->
|
||||||
|
[#xdata_field{
|
||||||
|
var = <<"jid">>,
|
||||||
|
values = [jid:encode(JID)]},
|
||||||
|
#xdata_field{
|
||||||
|
var = <<"hat_title">>,
|
||||||
|
values = [URI]},
|
||||||
|
#xdata_field{
|
||||||
|
var = <<"hat_uri">>,
|
||||||
|
values = [Title]}]
|
||||||
|
end, Hats),
|
||||||
|
Form =
|
||||||
|
#xdata{
|
||||||
|
title = translate:translate(
|
||||||
|
Lang, ?T("List of users with hats")),
|
||||||
|
type = result,
|
||||||
|
reported =
|
||||||
|
[#xdata_field{
|
||||||
|
label = translate:translate(Lang, ?T("Jabber ID")),
|
||||||
|
var = <<"jid">>},
|
||||||
|
#xdata_field{
|
||||||
|
label = translate:translate(Lang, ?T("Hat title")),
|
||||||
|
var = <<"hat_title">>},
|
||||||
|
#xdata_field{
|
||||||
|
label = translate:translate(Lang, ?T("Hat URI")),
|
||||||
|
var = <<"hat_uri">>}],
|
||||||
|
items = Items},
|
||||||
|
{result,
|
||||||
|
xmpp_util:make_adhoc_response(
|
||||||
|
Request,
|
||||||
|
#adhoc_command{
|
||||||
|
status = completed,
|
||||||
|
xdata = Form})};
|
||||||
|
{?MUC_HAT_LIST_CMD, _} ->
|
||||||
|
Txt = ?T("Incorrect value of 'action' attribute"),
|
||||||
|
{error, xmpp:err_bad_request(Txt, Lang)};
|
||||||
|
_ ->
|
||||||
|
{error, xmpp:err_item_not_found()}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{error, xmpp:err_forbidden()}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec add_hat(jid(), binary(), binary(), state()) ->
|
||||||
|
{ok, state()} | {error, size_limit}.
|
||||||
|
add_hat(JID, URI, Title, StateData) ->
|
||||||
|
Hats = StateData#state.hats_users,
|
||||||
|
LJID = jid:remove_resource(jid:tolower(JID)),
|
||||||
|
UserHats = maps:get(LJID, Hats, #{}),
|
||||||
|
UserHats2 = maps:put(URI, Title, UserHats),
|
||||||
|
USize = maps:size(UserHats2),
|
||||||
|
if
|
||||||
|
USize =< ?MAX_HATS_PER_USER ->
|
||||||
|
Hats2 = maps:put(LJID, UserHats2, Hats),
|
||||||
|
Size = maps:size(Hats2),
|
||||||
|
if
|
||||||
|
Size =< ?MAX_HATS_USERS ->
|
||||||
|
{ok, StateData#state{hats_users = Hats2}};
|
||||||
|
true ->
|
||||||
|
{error, size_limit}
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
{error, size_limit}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec del_hat(jid(), binary(), state()) -> state().
|
||||||
|
del_hat(JID, URI, StateData) ->
|
||||||
|
Hats = StateData#state.hats_users,
|
||||||
|
LJID = jid:remove_resource(jid:tolower(JID)),
|
||||||
|
UserHats = maps:get(LJID, Hats, #{}),
|
||||||
|
UserHats2 = maps:remove(URI, UserHats),
|
||||||
|
Hats2 =
|
||||||
|
case maps:size(UserHats2) of
|
||||||
|
0 ->
|
||||||
|
maps:remove(LJID, Hats);
|
||||||
|
_ ->
|
||||||
|
maps:put(LJID, UserHats2, Hats)
|
||||||
|
end,
|
||||||
|
StateData#state{hats_users = Hats2}.
|
||||||
|
|
||||||
|
-spec get_all_hats(state()) -> list({jid(), binary(), binary()}).
|
||||||
|
get_all_hats(StateData) ->
|
||||||
|
lists:flatmap(
|
||||||
|
fun({LJID, H}) ->
|
||||||
|
JID = jid:make(LJID),
|
||||||
|
lists:map(fun({URI, Title}) -> {JID, URI, Title} end,
|
||||||
|
maps:to_list(H))
|
||||||
|
end,
|
||||||
|
maps:to_list(StateData#state.hats_users)).
|
||||||
|
|
||||||
|
-spec add_presence_hats(jid(), #presence{}, state()) -> #presence{}.
|
||||||
|
add_presence_hats(JID, Pres, StateData) ->
|
||||||
|
case (StateData#state.config)#config.enable_hats of
|
||||||
|
true ->
|
||||||
|
Hats = StateData#state.hats_users,
|
||||||
|
LJID = jid:remove_resource(jid:tolower(JID)),
|
||||||
|
UserHats = maps:get(LJID, Hats, #{}),
|
||||||
|
case maps:size(UserHats) of
|
||||||
|
0 -> Pres;
|
||||||
|
_ ->
|
||||||
|
Items =
|
||||||
|
lists:map(fun({URI, Title}) ->
|
||||||
|
#muc_hat{uri = URI, title = Title}
|
||||||
|
end,
|
||||||
|
maps:to_list(UserHats)),
|
||||||
|
xmpp:set_subtag(Pres,
|
||||||
|
#muc_hats{hats = Items})
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
Pres
|
||||||
|
end.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
% Voice request support
|
% Voice request support
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user