aboutsummaryrefslogtreecommitdiff
path: root/src/mod_muc_room.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_muc_room.erl')
-rw-r--r--src/mod_muc_room.erl472
1 files changed, 459 insertions, 13 deletions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index 035e851fd..aaf3e8895 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -27,6 +27,8 @@
-author('alexey@process-one.net').
+-protocol({xep, 317, '0.1'}).
+
-behaviour(p1_fsm).
%% External exports
@@ -48,6 +50,7 @@
set_config/2,
get_state/1,
change_item/5,
+ change_item_async/5,
config_reloaded/1,
subscribe/4,
unsubscribe/2,
@@ -76,6 +79,12 @@
-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).
-ifdef(DBGFSM).
@@ -194,6 +203,11 @@ change_item(Pid, JID, Type, AffiliationOrRole, Reason) ->
{error, notfound}
end.
+-spec change_item_async(pid(), jid(), affiliation | role, affiliation() | role(), binary()) -> ok.
+change_item_async(Pid, JID, Type, AffiliationOrRole, Reason) ->
+ p1_fsm:send_all_state_event(
+ Pid, {process_item_change, {JID, Type, AffiliationOrRole, Reason}, undefined}).
+
-spec get_state(pid()) -> {ok, state()} | {error, notfound | timeout}.
get_state(Pid) ->
try p1_fsm:sync_send_all_state_event(Pid, get_state)
@@ -298,7 +312,8 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType])
room_shaper = Shaper}),
add_to_log(room_existence, started, State),
ejabberd_hooks:run(start_room, ServerHost, [ServerHost, Room, Host]),
- {ok, normal_state, reset_hibernate_timer(State)}.
+ State1 = cleanup_affiliations(State),
+ {ok, normal_state, reset_hibernate_timer(State1)}.
normal_state({route, <<"">>,
#message{from = From, type = Type, lang = Lang} = Packet},
@@ -446,6 +461,8 @@ normal_state({route, <<"">>,
process_iq_mucsub(From, IQ, StateData);
#xcaptcha{} ->
process_iq_captcha(From, IQ, StateData);
+ #adhoc_command{} ->
+ process_iq_adhoc(From, IQ, StateData);
_ ->
Txt = ?T("The feature requested is not "
"supported by the conference"),
@@ -664,6 +681,16 @@ handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
NewStateData = set_affiliations(Affiliations, StateData),
{next_state, StateName, NewStateData};
+handle_event({process_item_change, Item, UJID}, StateName, StateData) ->
+ case process_item_change(Item, StateData, UJID) of
+ {error, _} ->
+ {next_state, StateName, StateData};
+ StateData ->
+ {next_state, StateName, StateData};
+ NSD ->
+ store_room(NSD),
+ {next_state, StateName, NSD}
+ end;
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -712,6 +739,8 @@ handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData
case process_item_change(Item, StateData, UJID) of
{error, _} = Err ->
{reply, Err, StateName, StateData};
+ StateData ->
+ {reply, {ok, StateData}, StateName, StateData};
NSD ->
store_room(NSD),
{reply, {ok, NSD}, StateName, NSD}
@@ -1405,6 +1434,12 @@ is_occupant_or_admin(JID, StateData) ->
_ -> false
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
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-spec decide_fate_message(message(), jid(), state()) ->
@@ -1602,7 +1637,7 @@ do_get_affiliation_fallback(JID, StateData) ->
-spec get_affiliations(state()) -> affiliations().
get_affiliations(#state{config = #config{persistent = false}} = StateData) ->
- get_affiliations_callback(StateData);
+ get_affiliations_fallback(StateData);
get_affiliations(StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
@@ -1610,13 +1645,13 @@ get_affiliations(StateData) ->
Mod = gen_mod:db_mod(ServerHost, mod_muc),
case Mod:get_affiliations(ServerHost, Room, Host) of
{error, _} ->
- get_affiliations_callback(StateData);
+ get_affiliations_fallback(StateData);
{ok, Affiliations} ->
Affiliations
end.
--spec get_affiliations_callback(state()) -> affiliations().
-get_affiliations_callback(StateData) ->
+-spec get_affiliations_fallback(state()) -> affiliations().
+get_affiliations_fallback(StateData) ->
StateData#state.affiliations.
-spec get_service_affiliation(jid(), state()) -> owner | none.
@@ -1935,7 +1970,7 @@ filter_presence(Presence) ->
XMLNS = xmpp:get_ns(El),
case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
?NS_MUC -> false;
- _ -> true
+ _ -> XMLNS /= ?NS_HATS
end
end, xmpp:get_els(Presence)),
xmpp:set_els(Presence, Els).
@@ -2485,9 +2520,10 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
Pres = if Presence == undefined -> #presence{};
true -> Presence
end,
- Packet = xmpp:set_subtag(
- Pres, #muc_user{items = [Item],
- status_codes = StatusCodes}),
+ Packet = xmpp:set_subtag(
+ add_presence_hats(NJID, Pres, StateData),
+ #muc_user{items = [Item],
+ status_codes = StatusCodes}),
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node1, StateData),
Type = xmpp:get_type(Packet),
@@ -2536,7 +2572,9 @@ send_existing_presences1(ToJID, StateData) ->
false -> Item0
end,
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),
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
end
@@ -3579,7 +3617,8 @@ get_config(Lang, StateData, From) ->
{allow_voice_requests, Config#config.allow_voice_requests},
{allow_subscription, Config#config.allow_subscription},
{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
true ->
@@ -3667,6 +3706,7 @@ set_config(Opts, Config, ServerHost, Lang) ->
({maxusers, V}, C) -> C#config{max_users = V};
({enablelogging, V}, C) -> C#config{logging = V};
({pubsub, V}, C) -> C#config{pubsub = V};
+ ({enable_hats, V}, C) -> C#config{enable_hats = V};
({lang, L}, C) -> C#config{lang = L};
({captcha_whitelist, Js}, C) ->
LJIDs = [jid:tolower(J) || J <- Js],
@@ -3897,6 +3937,9 @@ set_opts([{Opt, Val} | Opts], StateData) ->
allow_subscription ->
StateData#state{config =
(StateData#state.config)#config{allow_subscription = Val}};
+ enable_hats ->
+ StateData#state{config =
+ (StateData#state.config)#config{enable_hats = Val}};
lang ->
StateData#state{config =
(StateData#state.config)#config{lang = Val}};
@@ -3927,6 +3970,11 @@ set_opts([{Opt, Val} | Opts], StateData) ->
end,
StateData#state{subject = Subj};
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
end,
set_opts(Opts, NSD).
@@ -3983,6 +4031,7 @@ make_opts(StateData) ->
?MAKE_CONFIG_OPT(#config.vcard),
?MAKE_CONFIG_OPT(#config.vcard_xupdate),
?MAKE_CONFIG_OPT(#config.pubsub),
+ ?MAKE_CONFIG_OPT(#config.enable_hats),
?MAKE_CONFIG_OPT(#config.lang),
{captcha_whitelist,
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
@@ -3990,6 +4039,9 @@ make_opts(StateData) ->
maps:to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
{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)},
{subscribers, Subscribers}].
@@ -4080,6 +4132,7 @@ maybe_forget_room(StateData) ->
make_disco_info(_From, StateData) ->
Config = StateData#state.config,
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
+ ?NS_COMMANDS,
?CONFIG_OPT_TO_FEATURE((Config#config.public),
<<"muc_public">>, <<"muc_hidden">>),
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
@@ -4120,6 +4173,77 @@ process_iq_disco_info(From, #iq{type = get, lang = Lang,
Extras = iq_disco_info_extras(Lang, StateData, false),
{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,
sub_els = [#disco_info{node = Node}]},
StateData) ->
try
@@ -4199,6 +4323,46 @@ process_iq_disco_items(From, #iq{type = get, sub_els = [#disco_items{node = <<>>
{result, #disco_items{}}
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) ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}.
@@ -4441,6 +4605,271 @@ get_mucroom_disco_items(StateData) ->
end, [], StateData#state.nicks),
#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
@@ -4687,7 +5116,7 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
id = p1_rand:get_string(),
sub_els = [Payload1]}]}}]},
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
- WJ, Packet1, true);
+ WJ, Packet1, false);
true -> ok
end,
if WN /= [] ->
@@ -4703,7 +5132,7 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
id = p1_rand:get_string(),
sub_els = [Payload2]}]}}]},
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
- WN, Packet2, true);
+ WN, Packet2, false);
true -> ok
end.
@@ -4927,6 +5356,23 @@ muc_subscribers_put(Subscriber, MUCSubscribers) ->
subscriber_nodes = NewSubNodes}.
+cleanup_affiliations(State) ->
+ case mod_muc_opt:cleanup_affiliations_on_start(State#state.server_host) of
+ true ->
+ Affiliations =
+ maps:filter(
+ fun({LUser, LServer, _}, _) ->
+ case ejabberd_router:is_my_host(LServer) of
+ true ->
+ ejabberd_auth:user_exists(LUser, LServer);
+ false ->
+ true
+ end
+ end, State#state.affiliations),
+ State#state{affiliations = Affiliations};
+ false ->
+ State
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaningful content