diff options
Diffstat (limited to 'src/mod_muc_room.erl')
-rw-r--r-- | src/mod_muc_room.erl | 571 |
1 files changed, 354 insertions, 217 deletions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 0b0311059..24bc414e2 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -39,7 +39,21 @@ is_occupant_or_admin/2, route/2, expand_opts/1, - config_fields/0]). + config_fields/0, + destroy/1, + destroy/2, + shutdown/1, + get_config/1, + set_config/2, + get_state/1, + change_item/5, + config_reloaded/1, + subscribe/4, + unsubscribe/2, + is_subscribed/2, + get_subscribers/1, + service_message/2, + get_disco_item/4]). %% gen_fsm callbacks -export([init/1, @@ -77,44 +91,165 @@ -type fsm_stop() :: {stop, normal, state()}. -type fsm_next() :: {next_state, normal_state, state()}. -type fsm_transition() :: fsm_stop() | fsm_next(). - --export_type([state/0]). +-type disco_item_filter() :: only_non_empty | all | non_neg_integer(). +-type admin_action() :: {jid(), affiliation | role, affiliation() | role(), binary()}. +-export_type([state/0, disco_item_filter/0]). -callback set_affiliation(binary(), binary(), binary(), jid(), affiliation(), binary()) -> ok | {error, any()}. -callback set_affiliations(binary(), binary(), binary(), - map()) -> ok | {error, any()}. + affiliations()) -> ok | {error, any()}. -callback get_affiliation(binary(), binary(), binary(), binary(), binary()) -> {ok, affiliation()} | {error, any()}. --callback get_affiliations(binary(), binary(), binary()) -> {ok, map()} | {error, any()}. +-callback get_affiliations(binary(), binary(), binary()) -> {ok, affiliations()} | {error, any()}. -callback search_affiliation(binary(), binary(), binary(), affiliation()) -> {ok, [{ljid(), {affiliation(), binary()}}]} | {error, any()}. %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- +-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), jid(), binary(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType) -> p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType], ?FSMOPTS). +-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType], ?FSMOPTS). +-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), jid(), binary(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType) -> p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType], ?FSMOPTS). +-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType], ?FSMOPTS). +-spec destroy(pid()) -> ok. +destroy(Pid) -> + p1_fsm:send_all_state_event(Pid, destroy). + +-spec destroy(pid(), binary()) -> ok. +destroy(Pid, Reason) -> + p1_fsm:send_all_state_event(Pid, {destroy, Reason}). + +-spec shutdown(pid()) -> boolean(). +shutdown(Pid) -> + ejabberd_cluster:send(Pid, shutdown). + +-spec config_reloaded(pid()) -> boolean(). +config_reloaded(Pid) -> + ejabberd_cluster:send(Pid, config_reloaded). + +-spec get_config(pid()) -> {ok, config()} | {error, notfound | timeout}. +get_config(Pid) -> + try p1_fsm:sync_send_all_state_event(Pid, get_config) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec set_config(pid(), config()) -> {ok, config()} | {error, notfound | timeout}. +set_config(Pid, Config) -> + try p1_fsm:sync_send_all_state_event(Pid, {change_config, Config}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec change_item(pid(), jid(), affiliation | role, affiliation() | role(), binary()) -> + {ok, state()} | {error, notfound | timeout}. +change_item(Pid, JID, Type, AffiliationOrRole, Reason) -> + try p1_fsm:sync_send_all_state_event( + Pid, {process_item_change, {JID, Type, AffiliationOrRole, Reason}, undefined}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec get_state(pid()) -> {ok, state()} | {error, notfound | timeout}. +get_state(Pid) -> + try p1_fsm:sync_send_all_state_event(Pid, get_state) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec subscribe(pid(), jid(), binary(), [binary()]) -> {ok, [binary()]} | {error, binary()}. +subscribe(Pid, JID, Nick, Nodes) -> + try p1_fsm:sync_send_all_state_event(Pid, {muc_subscribe, JID, Nick, Nodes}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, ?T("Request has timed out")}; + _:{_, {p1_fsm, _, _}} -> + {error, ?T("Conference room does not exist")} + end. + +-spec unsubscribe(pid(), jid()) -> ok | {error, binary()}. +unsubscribe(Pid, JID) -> + try p1_fsm:sync_send_all_state_event(Pid, {muc_unsubscribe, JID}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, ?T("Request has timed out")}; + _:{_, {p1_fsm, _, _}} -> + {error, ?T("Conference room does not exist")} + end. + +-spec is_subscribed(pid(), jid()) -> {true, [binary()]} | false. +is_subscribed(Pid, JID) -> + try p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, JID}) + catch _:{_, {p1_fsm, _, _}} -> false + end. + +-spec get_subscribers(pid()) -> {ok, [jid()]} | {error, notfound | timeout}. +get_subscribers(Pid) -> + try p1_fsm:sync_send_all_state_event(Pid, get_subscribers) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec service_message(pid(), binary()) -> ok. +service_message(Pid, Text) -> + p1_fsm:send_all_state_event(Pid, {service_message, Text}). + +-spec get_disco_item(pid(), disco_item_filter(), jid(), binary()) -> + {ok, binary()} | {error, notfound | timeout}. +get_disco_item(Pid, Filter, JID, Lang) -> + Timeout = 100, + Time = erlang:system_time(millisecond), + Query = {get_disco_item, Filter, JID, Lang, Time+Timeout}, + try p1_fsm:sync_send_all_state_event(Pid, Query, Timeout) of + {item, Desc} -> + {ok, Desc}; + false -> + {error, notfound} + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- @@ -163,15 +298,12 @@ normal_state({route, <<"">>, true when Type == groupchat -> Activity = get_user_activity(From, StateData), Now = erlang:system_time(microsecond), - MinMessageInterval = trunc(gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, min_message_interval) - * 1000000), + MinMessageInterval = trunc(mod_muc_opt:min_message_interval(StateData#state.server_host) * 1000000), Size = element_size(Packet), {MessageShaper, MessageShaperInterval} = ejabberd_shaper:update(Activity#activity.message_shaper, Size), if Activity#activity.message /= undefined -> - ErrText = <<"Traffic rate limit is exceeded">>, + ErrText = ?T("Traffic rate limit is exceeded"), Err = xmpp:err_resource_constraint(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData}; @@ -231,9 +363,9 @@ normal_state({route, <<"">>, true when Type == error -> case is_user_online(From, StateData) of true -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, + ErrorText = ?T("It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room"), NewState = expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)), @@ -242,8 +374,8 @@ normal_state({route, <<"">>, {next_state, normal_state, StateData} end; true when Type == chat -> - ErrText = <<"It is not allowed to send private messages " - "to the conference">>, + ErrText = ?T("It is not allowed to send private messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData}; @@ -258,7 +390,7 @@ normal_state({route, <<"">>, StateData end}; true -> - ErrText = <<"Improper message type">>, + ErrText = ?T("Improper message type"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData}; @@ -300,8 +432,8 @@ normal_state({route, <<"">>, ?NS_CAPTCHA -> process_iq_captcha(From, IQ, StateData); _ -> - Txt = <<"The feature requested is not " - "supported by the conference">>, + Txt = ?T("The feature requested is not " + "supported by the conference"), {error, xmpp:err_service_unavailable(Txt, Lang)} end, {IQRes, NewStateData} = @@ -347,9 +479,7 @@ normal_state({route, Nick, #presence{from = From} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), Now = erlang:system_time(microsecond), MinPresenceInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_presence_interval) - * 1000000), + trunc(mod_muc_opt:min_presence_interval(StateData#state.server_host) * 1000000), if (Now >= Activity#activity.presence_time + MinPresenceInterval) and (Activity#activity.presence == undefined) -> NewActivity = Activity#activity{presence_time = Now}, @@ -375,9 +505,9 @@ normal_state({route, ToNick, case decide_fate_message(Packet, From, StateData) of {expulse_sender, Reason} -> ?DEBUG(Reason, []), - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, + ErrorText = ?T("It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room"), NewState = expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)), {next_state, normal_state, NewState}; @@ -388,14 +518,14 @@ normal_state({route, ToNick, is_user_online(From, StateData) orelse is_subscriber(From, StateData)} of {true, true} when Type == groupchat -> - ErrText = <<"It is not allowed to send private messages " - "of type \"groupchat\"">>, + ErrText = ?T("It is not allowed to send private messages " + "of type \"groupchat\""), Err = xmpp:err_bad_request(ErrText, Lang), ejabberd_router:route_error(Packet, Err); {true, true} -> case find_jids_by_nick(ToNick, StateData) of [] -> - ErrText = <<"Recipient is not in the conference room">>, + ErrText = ?T("Recipient is not in the conference room"), Err = xmpp:err_item_not_found(ErrText, Lang), ejabberd_router:route_error(Packet, Err); ToJIDs -> @@ -415,21 +545,23 @@ normal_state({route, ToNick, PrivMsg = xmpp:set_from( xmpp:set_subtag(Packet, X), FromNickJID), - [ejabberd_router:route(xmpp:set_to(PrivMsg, ToJID)) - || ToJID <- ToJIDs]; + lists:foreach( + fun(ToJID) -> + ejabberd_router:route(xmpp:set_to(PrivMsg, ToJID)) + end, ToJIDs); true -> - ErrText = <<"It is not allowed to send private messages">>, + ErrText = ?T("It is not allowed to send private messages"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Packet, Err) end end; {true, false} -> - ErrText = <<"Only occupants are allowed to send messages " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err); {false, _} -> - ErrText = <<"It is not allowed to send private messages">>, + ErrText = ?T("It is not allowed to send private messages"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Packet, Err) end, @@ -442,7 +574,7 @@ normal_state({route, ToNick, #user{nick = FromNick} when AllowQuery orelse ToNick == FromNick -> case find_jid_by_nick(ToNick, StateData) of false -> - ErrText = <<"Recipient is not in the conference room">>, + ErrText = ?T("Recipient is not in the conference room"), Err = xmpp:err_item_not_found(ErrText, Lang), ejabberd_router:route_error(Packet, Err); To -> @@ -465,13 +597,13 @@ normal_state({route, ToNick, end end; _ -> - ErrText = <<"Queries to the conference members are " - "not allowed in this room">>, + ErrText = ?T("Queries to the conference members are " + "not allowed in this room"), Err = xmpp:err_not_allowed(ErrText, Lang), ejabberd_router:route_error(Packet, Err) catch _:{badkey, _} -> - ErrText = <<"Only occupants are allowed to send queries " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send queries " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err) end, @@ -493,9 +625,7 @@ handle_event({service_message, Msg}, _StateName, {next_state, normal_state, NSD}; handle_event({destroy, Reason}, _StateName, StateData) -> - {result, undefined, stop} = - destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason}, - StateData), + _ = destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason}, StateData), ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", [jid:encode(StateData#state.jid), Reason]), add_to_log(room_existence, destroyed, StateData), @@ -521,7 +651,7 @@ handle_sync_event({get_disco_item, Filter, JID, Lang, Time}, _From, StateName, S false -> false end, - CurrentTime = erlang:monotonic_time(millisecond), + CurrentTime = erlang:system_time(millisecond), if CurrentTime < Time -> {reply, Reply, StateName, StateData}; true -> @@ -545,7 +675,13 @@ handle_sync_event({change_config, Config}, _From, {reply, {ok, NSD#state.config}, StateName, NSD}; handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) -> - erlang:put(muc_subscribers, NewStateData#state.subscribers), + Mod = gen_mod:db_mod(NewStateData#state.server_host, mod_muc), + case erlang:function_exported(Mod, get_subscribed_rooms, 3) of + true -> + ok; + _ -> + erlang:put(muc_subscribers, NewStateData#state.subscribers) + end, {reply, {ok, NewStateData}, StateName, NewStateData}; handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) -> case process_item_change(Item, StateData, UJID) of @@ -580,7 +716,7 @@ handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From, NewConfig = (NewState#state.config)#config{ captcha_protected = CaptchaRequired, password_protected = PasswordProtected}, - {reply, {error, <<"Request is ignored">>}, + {reply, {error, ?T("Request is ignored")}, NewState#state{config = NewConfig}}; {error, Err} -> {reply, {error, get_error_text(Err)}, StateName, StateData} @@ -592,7 +728,7 @@ handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) -> {result, _, NewState} -> {reply, ok, StateName, NewState}; {ignore, NewState} -> - {reply, {error, <<"Request is ignored">>}, NewState}; + {reply, {error, ?T("Request is ignored")}, NewState}; {error, Err} -> {reply, {error, get_error_text(Err)}, StateName, StateData} end; @@ -669,7 +805,7 @@ handle_info({captcha_failed, From}, normal_state, NewState = case maps:get(From, StateData#state.robots, passed) of {_Nick, Packet} -> Robots = maps:remove(From, StateData#state.robots), - Txt = <<"The CAPTCHA verification has failed">>, + Txt = ?T("The CAPTCHA verification has failed"), Lang = xmpp:get_lang(Packet), Err = xmpp:err_not_authorized(Txt, Lang), ejabberd_router:route_error(Packet, Err), @@ -688,13 +824,12 @@ handle_info({iq_reply, #iq{type = Type, sub_els = Els}, To, From)), {next_state, StateName, StateData}; handle_info({iq_reply, timeout, IQ}, StateName, StateData) -> - Txt = <<"Request has timed out">>, + Txt = ?T("Request has timed out"), Err = xmpp:err_recipient_unavailable(Txt, IQ#iq.lang), ejabberd_router:route_error(IQ, Err), {next_state, StateName, StateData}; handle_info(config_reloaded, StateName, StateData) -> - Max = gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, history_size), + Max = mod_muc_opt:history_size(StateData#state.server_host), History1 = StateData#state.history, Q1 = History1#lqueue.queue, Q2 = case p1_queue:len(Q1) of @@ -714,9 +849,9 @@ terminate(Reason, _StateName, ?INFO_MSG("Stopping MUC room ~s@~s", [Room, Host]), ReasonT = case Reason of shutdown -> - <<"You are being removed from the room " - "because of a system shutdown">>; - _ -> <<"Room terminates">> + ?T("You are being removed from the room " + "because of a system shutdown"); + _ -> ?T("Room terminates") end, Packet = #presence{ type = unavailable, @@ -725,17 +860,16 @@ terminate(Reason, _StateName, role = none}], status_codes = [332,110]}]}, maps:fold( - fun(LJID, Info, _) -> - Nick = Info#user.nick, + fun(_, #user{nick = Nick, jid = JID}, _) -> case Reason of shutdown -> send_wrapped(jid:replace_resource(StateData#state.jid, Nick), - Info#user.jid, Packet, + JID, Packet, ?NS_MUCSUB_NODES_PARTICIPANTS, StateData); _ -> ok end, - tab_remove_online_user(LJID, StateData) + tab_remove_online_user(JID, StateData) end, [], get_users_and_subscribers(StateData)), add_to_log(room_existence, stopped, StateData), case (StateData#state.config)#config.persistent of @@ -746,16 +880,18 @@ terminate(Reason, _StateName, end, mod_muc:room_destroyed(Host, Room, self(), LServer) catch ?EX_RULE(E, R, St) -> - mod_muc:room_destroyed(Host, Room, self(), LServer), - ?ERROR_MSG("Got exception on room termination: ~p", [{E, {R, ?EX_STACK(St)}}]) - end, - ok. + StackTrace = ?EX_STACK(St), + mod_muc:room_destroyed(Host, Room, self(), LServer), + ?ERROR_MSG("Got exception on room termination:~n** ~s", + [misc:format_exception(2, E, R, StackTrace)]) + end. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- -spec route(pid(), stanza()) -> ok. route(Pid, Packet) -> + ?DEBUG("Routing to MUC room ~p:~n~s", [Pid, xmpp:pp(Packet)]), #jid{lresource = Nick} = xmpp:get_to(Packet), p1_fsm:send_event(Pid, {route, Nick, Packet}). @@ -825,27 +961,27 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData Err = case (StateData#state.config)#config.allow_change_subj of true -> xmpp:err_forbidden( - <<"Only moderators and participants are " - "allowed to change the subject in this " - "room">>, Lang); + ?T("Only moderators and participants are " + "allowed to change the subject in this " + "room"), Lang); _ -> xmpp:err_forbidden( - <<"Only moderators are allowed to change " - "the subject in this room">>, Lang) + ?T("Only moderators are allowed to change " + "the subject in this room"), Lang) end, ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData} end; true -> - ErrText = <<"Visitors are not allowed to send messages " - "to all occupants">>, + ErrText = ?T("Visitors are not allowed to send messages " + "to all occupants"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData} end; false -> - ErrText = <<"Only occupants are allowed to send messages " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData} @@ -930,14 +1066,14 @@ process_voice_request(From, Pkt, StateData) -> send_voice_request(From, Lang, NSD), NSD; {ok, _, _} -> - ErrText = <<"Please, wait for a while before sending " - "new voice request">>, + ErrText = ?T("Please, wait for a while before sending " + "new voice request"), Err = xmpp:err_resource_constraint(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData#state{last_voice_request_time = Times} end; false -> - ErrText = <<"Voice requests are disabled in this conference">>, + ErrText = ?T("Voice requests are disabled in this conference"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData @@ -962,14 +1098,14 @@ process_voice_approval(From, Pkt, VoiceApproval, StateData) -> StateData end; false -> - ErrText = <<"Failed to extract JID from your voice " - "request approval">>, + ErrText = ?T("Failed to extract JID from your voice " + "request approval"), Err = xmpp:err_bad_request(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData end; false -> - ErrText = <<"Only moderators can approve voice requests">>, + ErrText = ?T("Only moderators can approve voice requests"), Err = xmpp:err_not_allowed(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData @@ -1060,27 +1196,32 @@ do_process_presence(Nick, #presence{from = From, type = available, lang = Lang} is_visitor(From, StateData)}} of {_, _, {false, true}} -> Packet1 = Packet#presence{sub_els = [#muc{}]}, - ErrText = <<"Visitors are not allowed to change their " - "nicknames in this room">>, + ErrText = ?T("Visitors are not allowed to change their " + "nicknames in this room"), Err = xmpp:err_not_allowed(ErrText, Lang), ejabberd_router:route_error(Packet1, Err), StateData; {true, _, _} -> Packet1 = Packet#presence{sub_els = [#muc{}]}, - ErrText = <<"That nickname is already in use by another " - "occupant">>, + ErrText = ?T("That nickname is already in use by another " + "occupant"), Err = xmpp:err_conflict(ErrText, Lang), ejabberd_router:route_error(Packet1, Err), StateData; {_, false, _} -> Packet1 = Packet#presence{sub_els = [#muc{}]}, - ErrText = <<"That nickname is registered by another " - "person">>, - Err = xmpp:err_conflict(ErrText, Lang), + Err = case Nick of + <<>> -> + xmpp:err_jid_malformed(?T("Nickname can't be empty"), + Lang); + _ -> + xmpp:err_conflict(?T("That nickname is registered" + " by another person"), Lang) + end, ejabberd_router:route_error(Packet1, Err), StateData; _ -> - change_nick(From, Nick, StateData) + change_nick(From, Nick, StateData) end; false -> Stanza = maybe_strip_status_from_presence( @@ -1122,9 +1263,9 @@ do_process_presence(Nick, #presence{from = From, type = unavailable} = Packet, remove_online_user(From, NewState, Reason); do_process_presence(_Nick, #presence{from = From, type = error, lang = Lang} = Packet, StateData) -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, + ErrorText = ?T("It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room"), expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)). @@ -1153,7 +1294,7 @@ close_room_if_temporary_and_empty(StateData1) -> _ -> {next_state, normal_state, StateData1} end. --spec get_users_and_subscribers(state()) -> map(). +-spec get_users_and_subscribers(state()) -> users(). get_users_and_subscribers(StateData) -> OnlineSubscribers = maps:fold( fun(LJID, _, Acc) -> @@ -1338,7 +1479,7 @@ set_affiliation_fallback(JID, Affiliation, StateData, Reason) -> end, StateData#state{affiliations = Affiliations}. --spec set_affiliations(map(), state()) -> state(). +-spec set_affiliations(affiliations(), state()) -> state(). set_affiliations(Affiliations, #state{config = #config{persistent = false}} = StateData) -> set_affiliations_fallback(Affiliations, StateData); @@ -1354,7 +1495,7 @@ set_affiliations(Affiliations, StateData) -> set_affiliations_fallback(Affiliations, StateData) end. --spec set_affiliations_fallback(map(), state()) -> state(). +-spec set_affiliations_fallback(affiliations(), state()) -> state(). set_affiliations_fallback(Affiliations, StateData) -> StateData#state{affiliations = Affiliations}. @@ -1372,7 +1513,7 @@ get_affiliation(#jid{} = JID, StateData) -> get_affiliation(LJID, StateData) -> get_affiliation(jid:make(LJID), StateData). --spec do_get_affiliation(jid(), state()) -> affiliation(). +-spec do_get_affiliation(jid(), state()) -> affiliation() | {affiliation(), binary()}. do_get_affiliation(JID, #state{config = #config{persistent = false}} = StateData) -> do_get_affiliation_fallback(JID, StateData); do_get_affiliation(JID, StateData) -> @@ -1389,7 +1530,7 @@ do_get_affiliation(JID, StateData) -> Affiliation end. --spec do_get_affiliation_fallback(jid(), state()) -> affiliation(). +-spec do_get_affiliation_fallback(jid(), state()) -> affiliation() | {affiliation(), binary()}. do_get_affiliation_fallback(JID, StateData) -> LJID = jid:tolower(JID), try maps:get(LJID, StateData#state.affiliations) @@ -1408,7 +1549,7 @@ do_get_affiliation_fallback(JID, StateData) -> end end. --spec get_affiliations(state()) -> map(). +-spec get_affiliations(state()) -> affiliations(). get_affiliations(#state{config = #config{persistent = false}} = StateData) -> get_affiliations_callback(StateData); get_affiliations(StateData) -> @@ -1423,7 +1564,7 @@ get_affiliations(StateData) -> Affiliations end. --spec get_affiliations_callback(state()) -> map(). +-spec get_affiliations_callback(state()) -> affiliations(). get_affiliations_callback(StateData) -> StateData#state.affiliations. @@ -1529,29 +1670,19 @@ get_max_users(StateData) -> -spec get_service_max_users(state()) -> pos_integer(). get_service_max_users(StateData) -> - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users). + mod_muc_opt:max_users(StateData#state.server_host). -spec get_max_users_admin_threshold(state()) -> pos_integer(). get_max_users_admin_threshold(StateData) -> - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users_admin_threshold). + mod_muc_opt:max_users_admin_threshold(StateData#state.server_host). --spec room_queue_new(binary(), ejabberd_shaper:shaper(), _) -> p1_queue:queue(). +-spec room_queue_new(binary(), ejabberd_shaper:shaper(), _) -> p1_queue:queue({message | presence, jid()}) | undefined. room_queue_new(ServerHost, Shaper, QueueType) -> HaveRoomShaper = Shaper /= none, - HaveMessageShaper = gen_mod:get_module_opt( - ServerHost, mod_muc, - user_message_shaper) /= none, - HavePresenceShaper = gen_mod:get_module_opt( - ServerHost, mod_muc, - user_presence_shaper) /= none, - HaveMinMessageInterval = gen_mod:get_module_opt( - ServerHost, mod_muc, - min_message_interval) /= 0, - HaveMinPresenceInterval = gen_mod:get_module_opt( - ServerHost, mod_muc, - min_presence_interval) /= 0, + HaveMessageShaper = mod_muc_opt:user_message_shaper(ServerHost) /= none, + HavePresenceShaper = mod_muc_opt:user_presence_shaper(ServerHost) /= none, + HaveMinMessageInterval = mod_muc_opt:min_message_interval(ServerHost) /= 0, + HaveMinPresenceInterval = mod_muc_opt:min_presence_interval(ServerHost) /= 0, if HaveRoomShaper or HaveMessageShaper or HavePresenceShaper or HaveMinMessageInterval or HaveMinPresenceInterval -> p1_queue:new(QueueType); @@ -1567,11 +1698,9 @@ get_user_activity(JID, StateData) -> {ok, _P, A} -> A; error -> MessageShaper = - ejabberd_shaper:new(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, user_message_shaper)), + ejabberd_shaper:new(mod_muc_opt:user_message_shaper(StateData#state.server_host)), PresenceShaper = - ejabberd_shaper:new(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, user_presence_shaper)), + ejabberd_shaper:new(mod_muc_opt:user_presence_shaper(StateData#state.server_host)), #activity{message_shaper = MessageShaper, presence_shaper = PresenceShaper} end. @@ -1579,13 +1708,9 @@ get_user_activity(JID, StateData) -> -spec store_user_activity(jid(), #activity{}, state()) -> state(). store_user_activity(JID, UserActivity, StateData) -> MinMessageInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_message_interval) - * 1000), + trunc(mod_muc_opt:min_message_interval(StateData#state.server_host) * 1000), MinPresenceInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_presence_interval) - * 1000), + trunc(mod_muc_opt:min_presence_interval(StateData#state.server_host) * 1000), Key = jid:tolower(JID), Now = erlang:system_time(microsecond), Activity1 = clean_treap(StateData#state.activity, @@ -1703,14 +1828,10 @@ update_online_user(JID, #user{nick = Nick} = User, StateData) -> end, NewStateData. +-spec set_subscriber(jid(), binary(), [binary()], state()) -> state(). set_subscriber(JID, Nick, Nodes, #state{room = Room, host = Host, server_host = ServerHost} = StateData) -> - BareJID = case JID of - #jid{} -> jid:remove_resource(JID); - _ -> - ?ERROR_MSG("Invalid subscriber JID in set_subscriber ~p", [JID]), - jid:remove_resource(jid:make(JID)) - end, + BareJID = jid:remove_resource(JID), LBareJID = jid:tolower(BareJID), Subscribers = maps:put(LBareJID, #subscriber{jid = BareJID, @@ -1888,8 +2009,7 @@ add_new_user(From, Nick, Packet, StateData) -> StateData), NConferences = tab_count_user(From, StateData), MaxConferences = - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_user_conferences), + mod_muc_opt:max_user_conferences(StateData#state.server_host), Collision = nick_collision(From, Nick, StateData), IsSubscribeRequest = not is_record(Packet, presence), case {(ServiceAffiliation == owner orelse @@ -1903,7 +2023,7 @@ add_new_user(From, Nick, Packet, StateData) -> get_default_role(Affiliation, StateData)} of {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers -> - Txt = <<"Too many users in this conference">>, + Txt = ?T("Too many users in this conference"), Err = xmpp:err_resource_constraint(Txt, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -1912,7 +2032,7 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; {false, _, _, _} when NConferences >= MaxConferences -> - Txt = <<"You have joined too many conferences">>, + Txt = ?T("You have joined too many conferences"), Err = xmpp:err_resource_constraint(Txt, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -1931,10 +2051,10 @@ add_new_user(From, Nick, Packet, StateData) -> {_, _, _, none} -> Err = case Affiliation of outcast -> - ErrText = <<"You have been banned from this room">>, + ErrText = ?T("You have been banned from this room"), xmpp:err_forbidden(ErrText, Lang); _ -> - ErrText = <<"Membership is required to enter this room">>, + ErrText = ?T("Membership is required to enter this room"), xmpp:err_registration_required(ErrText, Lang) end, if not IsSubscribeRequest -> @@ -1944,7 +2064,7 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; {_, true, _, _} -> - ErrText = <<"That nickname is already in use by another occupant">>, + ErrText = ?T("That nickname is already in use by another occupant"), Err = xmpp:err_conflict(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -1953,8 +2073,14 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; {_, _, false, _} -> - ErrText = <<"That nickname is registered by another person">>, - Err = xmpp:err_conflict(ErrText, Lang), + Err = case Nick of + <<>> -> + xmpp:err_jid_malformed(?T("Nickname can't be empty"), + Lang); + _ -> + xmpp:err_conflict(?T("That nickname is registered" + " by another person"), Lang) + end, if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), StateData; @@ -1991,7 +2117,7 @@ add_new_user(From, Nick, Packet, StateData) -> true -> {result, subscribe_result(Packet), ResultState} end; need_password -> - ErrText = <<"A password is required to enter this room">>, + ErrText = ?T("A password is required to enter this room"), Err = xmpp:err_not_authorized(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2022,7 +2148,7 @@ add_new_user(From, Nick, Packet, StateData) -> {ignore, NewState} end; {error, limit} -> - ErrText = <<"Too many CAPTCHA requests">>, + ErrText = ?T("Too many CAPTCHA requests"), Err = xmpp:err_resource_constraint(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2031,7 +2157,7 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; _ -> - ErrText = <<"Unable to generate a CAPTCHA">>, + ErrText = ?T("Unable to generate a CAPTCHA"), Err = xmpp:err_internal_server_error(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2041,7 +2167,7 @@ add_new_user(From, Nick, Packet, StateData) -> end end; _ -> - ErrText = <<"Incorrect password">>, + ErrText = ?T("Incorrect password"), Err = xmpp:err_not_authorized(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2121,7 +2247,7 @@ extract_password(#iq{} = IQ) -> false end. --spec get_history(binary(), stanza(), state()) -> lqueue(). +-spec get_history(binary(), stanza(), state()) -> [lqueue_elem()]. get_history(Nick, Packet, #state{history = History}) -> case xmpp:get_subtag(Packet, #muc{}) of #muc{history = #muc_history{} = MUCHistory} -> @@ -2132,8 +2258,8 @@ get_history(Nick, Packet, #state{history = History}) -> p1_queue:to_list(History#lqueue.queue) end. --spec filter_history(p1_queue:queue(), erlang:timestamp(), - binary(), muc_history()) -> list(). +-spec filter_history(p1_queue:queue(lqueue_elem()), erlang:timestamp(), + binary(), muc_history()) -> [lqueue_elem()]. filter_history(Queue, Now, Nick, #muc_history{since = Since, seconds = Seconds, @@ -2158,9 +2284,7 @@ filter_history(Queue, Now, Nick, -spec is_room_overcrowded(state()) -> boolean(). is_room_overcrowded(StateData) -> - MaxUsersPresence = gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_users_presence), + MaxUsersPresence = mod_muc_opt:max_users_presence(StateData#state.server_host), maps:size(StateData#state.users) > MaxUsersPresence. -spec presence_broadcast_allowed(jid(), state()) -> boolean(). @@ -2516,7 +2640,7 @@ status_codes(_IsInitialPresence, _IsSelfPresence = false, _StateData) -> []. lqueue_new(Max, Type) -> #lqueue{queue = p1_queue:new(Type), max = Max}. --spec lqueue_in(term(), lqueue()) -> lqueue(). +-spec lqueue_in(lqueue_elem(), lqueue()) -> lqueue(). %% If the message queue limit is set to 0, do not store messages. lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ; %% Otherwise, rotate messages in the queue store. @@ -2529,7 +2653,7 @@ lqueue_in(Item, #lqueue{queue = Q1, max = Max}) -> true -> #lqueue{queue = Q2, max = Max} end. --spec lqueue_cut(p1_queue:queue(), non_neg_integer()) -> p1_queue:queue(). +-spec lqueue_cut(p1_queue:queue(lqueue_elem()), non_neg_integer()) -> p1_queue:queue(lqueue_elem()). lqueue_cut(Q, 0) -> Q; lqueue_cut(Q, N) -> {_, Q1} = p1_queue:out(Q), @@ -2564,7 +2688,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) -> StateData end. --spec send_history(jid(), list(), state()) -> ok. +-spec send_history(jid(), [lqueue_elem()], state()) -> ok. send_history(JID, History, StateData) -> lists:foreach( fun({Nick, Packet, _HaveSubject, _TimeStamp, _Size}) -> @@ -2608,7 +2732,7 @@ can_change_subject(Role, IsSubscriber, StateData) -> {result, muc_admin()}. process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]}, _StateData) -> - Txt = <<"No 'item' element found">>, + Txt = ?T("No 'item' element found"), {error, xmpp:err_bad_request(Txt, Lang)}; process_iq_admin(From, #iq{type = set, lang = Lang, sub_els = [#muc_admin{items = Items}]}, @@ -2621,7 +2745,7 @@ process_iq_admin(From, #iq{type = get, lang = Lang, FRole = get_role(From, StateData), case Item of #muc_item{role = undefined, affiliation = undefined} -> - Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + Txt = ?T("Neither 'role' nor 'affiliation' attribute found"), {error, xmpp:err_bad_request(Txt, Lang)}; #muc_item{role = undefined, affiliation = Affiliation} -> if (FAffiliation == owner) or @@ -2631,7 +2755,7 @@ process_iq_admin(From, #iq{type = get, lang = Lang, Items = items_with_affiliation(Affiliation, StateData), {result, #muc_admin{items = Items}}; true -> - ErrText = <<"Administrator privileges required">>, + ErrText = ?T("Administrator privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)} end; #muc_item{role = Role} -> @@ -2639,12 +2763,12 @@ process_iq_admin(From, #iq{type = get, lang = Lang, Items = items_with_role(Role, StateData), {result, #muc_admin{items = Items}}; true -> - ErrText = <<"Moderator privileges required">>, + ErrText = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)} end end; process_iq_admin(_From, #iq{type = get, lang = Lang}, _StateData) -> - ErrText = <<"Too many <item/> elements">>, + ErrText = ?T("Too many <item/> elements"), {error, xmpp:err_bad_request(ErrText, Lang)}. -spec items_with_role(role(), state()) -> [muc_item()]. @@ -2734,7 +2858,8 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> {error, Err} -> {error, Err} end. --spec process_item_change(jid()) -> function(). +-spec process_item_change(jid()) -> fun((admin_action(), state() | {error, stanza_error()}) -> + state() | {error, stanza_error()}). process_item_change(UJID) -> fun(_, {error, _} = Err) -> Err; @@ -2742,9 +2867,6 @@ process_item_change(UJID) -> process_item_change(Item, SD, UJID) end. --type admin_action() :: {jid(), affiliation | role, - affiliation() | role(), binary()}. - -spec process_item_change(admin_action(), state(), undefined | jid()) -> state() | {error, stanza_error()}. process_item_change(Item, SD, UJID) -> try case Item of @@ -2795,15 +2917,17 @@ process_item_change(Item, SD, UJID) -> SD1 end catch ?EX_RULE(E, R, St) -> - FromSuffix = case UJID of - #jid{} -> - JidString = jid:encode(UJID), - <<" from ", JidString/binary>>; - undefined -> - <<"">> - end, - ?ERROR_MSG("failed to set item ~p~s: ~p", - [Item, FromSuffix, {E, {R, ?EX_STACK(St)}}]), + StackTrace = ?EX_STACK(St), + FromSuffix = case UJID of + #jid{} -> + JidString = jid:encode(UJID), + <<" from ", JidString/binary>>; + undefined -> + <<"">> + end, + ?ERROR_MSG("Failed to set item ~p~s:~n** ~s", + [Item, FromSuffix, + misc:format_exception(2, E, R, StackTrace)]), {error, xmpp:err_internal_server_error()} end. @@ -2816,12 +2940,12 @@ find_changed_items(_UJID, _UAffiliation, _URole, [], find_changed_items(_UJID, _UAffiliation, _URole, [#muc_item{jid = undefined, nick = <<"">>}|_], Lang, _StateData, _Res) -> - Txt = <<"Neither 'jid' nor 'nick' attribute found">>, + Txt = ?T("Neither 'jid' nor 'nick' attribute found"), throw({error, xmpp:err_bad_request(Txt, Lang)}); find_changed_items(_UJID, _UAffiliation, _URole, [#muc_item{role = undefined, affiliation = undefined}|_], Lang, _StateData, _Res) -> - Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + Txt = ?T("Neither 'role' nor 'affiliation' attribute found"), throw({error, xmpp:err_bad_request(Txt, Lang)}); find_changed_items(UJID, UAffiliation, URole, [#muc_item{jid = J, nick = Nick, reason = Reason, @@ -2833,7 +2957,7 @@ find_changed_items(UJID, UAffiliation, URole, Nick /= <<"">> -> case find_jids_by_nick(Nick, StateData) of [] -> - ErrText = {<<"Nickname ~s does not exist in the room">>, + ErrText = {?T("Nickname ~s does not exist in the room"), [Nick]}, throw({error, xmpp:err_not_acceptable(ErrText, Lang)}); JIDList -> @@ -2889,7 +3013,7 @@ find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, MoreRes ++ Res); false -> - Txt = <<"Changing role/affiliation is not allowed">>, + Txt = ?T("Changing role/affiliation is not allowed"), throw({error, xmpp:err_not_allowed(Txt, Lang)}) end. @@ -3085,8 +3209,8 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, _ -> [] end end, - lists:foreach(fun (J) -> - #user{nick = Nick} = maps:get(J, StateData#state.users), + lists:foreach(fun (LJ) -> + #user{nick = Nick, jid = J} = maps:get(LJ, StateData#state.users), add_to_log(kickban, {Nick, Reason, Code}, StateData), tab_remove_online_user(J, StateData), send_kickban_presence1(UJID, J, Reason, Code, @@ -3144,6 +3268,7 @@ get_actor_nick(MJID, StateData) -> catch _:{badkey, _} -> <<"">> end. +-spec convert_legacy_fields([xdata_field()]) -> [xdata_field()]. convert_legacy_fields(Fs) -> lists:map( fun(#xdata_field{var = Var} = F) -> @@ -3181,7 +3306,7 @@ process_iq_owner(From, #iq{type = set, lang = Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), if FAffiliation /= owner -> - ErrText = <<"Owner privileges required">>, + ErrText = ?T("Owner privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)}; Destroy /= undefined, Config == undefined, Items == [] -> ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", @@ -3211,7 +3336,7 @@ process_iq_owner(From, #iq{type = set, lang = Lang, {error, xmpp:err_bad_request(Txt, Lang)} end; _ -> - Txt = <<"Incorrect data form">>, + Txt = ?T("Incorrect data form"), {error, xmpp:err_bad_request(Txt, Lang)} end; Items /= [], Config == undefined, Destroy == undefined -> @@ -3226,7 +3351,7 @@ process_iq_owner(From, #iq{type = get, lang = Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), if FAffiliation /= owner -> - ErrText = <<"Owner privileges required">>, + ErrText = ?T("Owner privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)}; Destroy == undefined, Config == undefined -> case Items of @@ -3234,13 +3359,13 @@ process_iq_owner(From, #iq{type = get, lang = Lang, {result, #muc_owner{config = get_config(Lang, StateData, From)}}; [#muc_item{affiliation = undefined}] -> - Txt = <<"No 'affiliation' attribute found">>, + Txt = ?T("No 'affiliation' attribute found"), {error, xmpp:err_bad_request(Txt, Lang)}; [#muc_item{affiliation = Affiliation}] -> Items = items_with_affiliation(Affiliation, StateData), {result, #muc_owner{items = Items}}; [_|_] -> - Txt = <<"Too many <item/> elements">>, + Txt = ?T("Too many <item/> elements"), {error, xmpp:err_bad_request(Txt, Lang)} end; true -> @@ -3291,12 +3416,8 @@ is_allowed_mam_change(Options, StateData, From) -> is_allowed_room_name_desc_limits(Options, StateData) -> RoomName = proplists:get_value(roomname, Options, <<"">>), RoomDesc = proplists:get_value(roomdesc, Options, <<"">>), - MaxRoomName = gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_room_name), - MaxRoomDesc = gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_room_desc), + MaxRoomName = mod_muc_opt:max_room_name(StateData#state.server_host), + MaxRoomDesc = mod_muc_opt:max_room_desc(StateData#state.server_host), (byte_size(RoomName) =< MaxRoomName) andalso (byte_size(RoomDesc) =< MaxRoomDesc). @@ -3320,8 +3441,7 @@ is_password_settings_correct(Options, StateData) -> -spec get_default_room_maxusers(state()) -> non_neg_integer(). get_default_room_maxusers(RoomState) -> DefRoomOpts = - gen_mod:get_module_opt(RoomState#state.server_host, - mod_muc, default_room_options), + mod_muc_opt:default_room_options(RoomState#state.server_host), RoomState2 = set_opts(DefRoomOpts, RoomState), (RoomState2#state.config)#config.max_users. @@ -3334,7 +3454,7 @@ get_config(Lang, StateData, From) -> Config = StateData#state.config, MaxUsersRoom = get_max_users(StateData), Title = str:format( - translate:translate(Lang, <<"Configuration of room ~s">>), + translate:translate(Lang, ?T("Configuration of room ~s")), [jid:encode(StateData#state.jid)]), Fs = [{roomname, Config#config.title}, {roomdesc, Config#config.description}, @@ -3352,7 +3472,7 @@ get_config(Lang, StateData, From) -> end}, {maxusers, MaxUsersRoom, [if is_integer(ServiceMaxUsers) -> []; - true -> [{<<"No limit">>, <<"none">>}] + true -> [{?T("No limit"), <<"none">>}] end] ++ [{integer_to_binary(N), N} || N <- lists:usort([ServiceMaxUsers, DefaultRoomMaxUsers, @@ -3419,6 +3539,7 @@ set_config(Options, StateData, Lang) -> Err end. +-spec get_config_opt_name(pos_integer()) -> atom(). get_config_opt_name(Pos) -> Fs = [config|record_info(fields, config)], lists:nth(Pos, Fs). @@ -3472,7 +3593,7 @@ set_config(Opts, Config, ServerHost, Lang) -> {0, undefined} -> ?ERROR_MSG("set_room_option hook failed for " "option '~s' with value ~p", [O, V]), - Txt = {<<"Failed to process option '~s'">>, [O]}, + Txt = {?T("Failed to process option '~s'"), [O]}, {error, xmpp:err_internal_server_error(Txt, Lang)}; {Pos, Val} -> setelement(Pos, C, Val) @@ -3727,6 +3848,7 @@ set_opts([{Opt, Val} | Opts], StateData) -> end, set_opts(Opts, NSD). +-spec set_vcard_xupdate(state()) -> state(). set_vcard_xupdate(#state{config = #config{vcard = VCardRaw, vcard_xupdate = undefined} = Config} = State) @@ -3835,6 +3957,7 @@ destroy_room(DEl, StateData) -> maybe_forget_room(StateData), {result, undefined, stop}. +-spec maybe_forget_room(state()) -> state(). maybe_forget_room(StateData) -> Forget = case (StateData#state.config)#config.persistent of true -> @@ -3897,7 +4020,7 @@ make_disco_info(_From, StateData) -> -spec process_iq_disco_info(jid(), iq(), state()) -> {result, disco_info()} | {error, stanza_error()}. process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), {error, xmpp:err_not_allowed(Txt, Lang)}; process_iq_disco_info(From, #iq{type = get, lang = Lang, sub_els = [#disco_info{node = <<>>}]}, @@ -3917,7 +4040,7 @@ process_iq_disco_info(From, #iq{type = get, lang = Lang, Node = <<(ejabberd_config:get_uri())/binary, $#, Hash/binary>>, {result, DiscoInfo1#disco_info{node = Node}} catch _:{badmatch, _} -> - Txt = <<"Invalid node name">>, + Txt = ?T("Invalid node name"), {error, xmpp:err_item_not_found(Txt, Lang)} end. @@ -3957,7 +4080,7 @@ iq_disco_info_extras(Lang, StateData, Static) -> -spec process_iq_disco_items(jid(), iq(), state()) -> {error, stanza_error()} | {result, disco_items()}. process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), {error, xmpp:err_not_allowed(Txt, Lang)}; process_iq_disco_items(From, #iq{type = get}, StateData) -> case (StateData#state.config)#config.public_list of @@ -3978,17 +4101,17 @@ process_iq_disco_items(From, #iq{type = get}, StateData) -> -spec process_iq_captcha(jid(), iq(), state()) -> {error, stanza_error()} | {result, undefined}. process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), {error, xmpp:err_not_allowed(Txt, Lang)}; process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]}, _StateData) -> case ejabberd_captcha:process_reply(SubEl) of ok -> {result, undefined}; {error, malformed} -> - Txt = <<"Incorrect CAPTCHA submit">>, + Txt = ?T("Incorrect CAPTCHA submit"), {error, xmpp:err_bad_request(Txt, Lang)}; _ -> - Txt = <<"The CAPTCHA verification has failed">>, + Txt = ?T("The CAPTCHA verification has failed"), {error, xmpp:err_not_allowed(Txt, Lang)} end. @@ -4015,7 +4138,7 @@ process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [Pkt]}, NewConfig = Config#config{vcard = VCardRaw, vcard_xupdate = Hash}, change_config(NewConfig, StateData); _ -> - ErrText = <<"Owner privileges required">>, + ErrText = ?T("Owner privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)} end. @@ -4026,7 +4149,7 @@ process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [Pkt]}, process_iq_mucsub(_From, #iq{type = set, lang = Lang, sub_els = [#muc_subscribe{}]}, #state{just_created = Just, config = #config{allow_subscription = false}}) when Just /= true -> - {error, xmpp:err_not_allowed(<<"Subscriptions are not allowed">>, Lang)}; + {error, xmpp:err_not_allowed(?T("Subscriptions are not allowed"), Lang)}; process_iq_mucsub(From, #iq{type = set, lang = Lang, sub_els = [#muc_subscribe{jid = #jid{} = SubJid} = Mucsub]}, @@ -4039,7 +4162,7 @@ process_iq_mucsub(From, sub_els = [Mucsub#muc_subscribe{jid = undefined}]}, StateData); true -> - Txt = <<"Moderator privileges required">>, + Txt = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(Txt, Lang)} end; process_iq_mucsub(From, @@ -4055,11 +4178,18 @@ process_iq_mucsub(From, StateData#state.host, From, Nick)} of {true, _} -> - ErrText = <<"That nickname is already in use by another occupant">>, + ErrText = ?T("That nickname is already in use by another occupant"), {error, xmpp:err_conflict(ErrText, Lang)}; {_, false} -> - ErrText = <<"That nickname is registered by another person">>, - {error, xmpp:err_conflict(ErrText, Lang)}; + Err = case Nick of + <<>> -> + xmpp:err_jid_malformed(?T("Nickname can't be empty"), + Lang); + _ -> + xmpp:err_conflict(?T("That nickname is registered" + " by another person"), Lang) + end, + {error, Err}; _ -> NewStateData = set_subscriber(From, Nick, Nodes, StateData), {result, subscribe_result(Packet), NewStateData} @@ -4083,7 +4213,7 @@ process_iq_mucsub(From, #iq{type = set, lang = Lang, sub_els = [#muc_unsubscribe{jid = undefined}]}, StateData); true -> - Txt = <<"Moderator privileges required">>, + Txt = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(Txt, Lang)} end; process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]}, @@ -4129,13 +4259,14 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang, end, [], StateData#state.subscribers), {result, #muc_subscriptions{list = Subs}, StateData}; _ -> - Txt = <<"Moderator privileges required">>, + Txt = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(Txt, Lang)} end; process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), {error, xmpp:err_bad_request(Txt, Lang)}. +-spec remove_subscriptions(state()) -> state(). remove_subscriptions(StateData) -> if not (StateData#state.config)#config.allow_subscription -> StateData#state{subscribers = #{}, @@ -4188,7 +4319,7 @@ get_roomdesc_reply(JID, StateData, Tail) -> get_roomdesc_tail(StateData, Lang) -> Desc = case (StateData#state.config)#config.public of true -> <<"">>; - _ -> translate:translate(Lang, <<"private, ">>) + _ -> translate:translate(Lang, ?T("private, ")) end, Len = maps:size(StateData#state.nicks), <<" (", Desc/binary, (integer_to_binary(Len))/binary, ")">>. @@ -4209,9 +4340,9 @@ get_mucroom_disco_items(StateData) -> -spec prepare_request_form(jid(), binary(), binary()) -> message(). prepare_request_form(Requester, Nick, Lang) -> - Title = translate:translate(Lang, <<"Voice request">>), + Title = translate:translate(Lang, ?T("Voice request")), Instruction = translate:translate( - Lang, <<"Either approve or decline the voice request.">>), + Lang, ?T("Either approve or decline the voice request.")), Fs = muc_request:encode([{role, participant}, {jid, Requester}, {roomnick, Nick}, @@ -4253,11 +4384,11 @@ check_invitation(From, Invitations, Lang, StateData) -> true -> ok; false -> - Txt = <<"No 'to' attribute found in the invitation">>, + Txt = ?T("No 'to' attribute found in the invitation"), {error, xmpp:err_bad_request(Txt, Lang)} end; false -> - Txt = <<"Invitations are not allowed in this conference">>, + Txt = ?T("Invitations are not allowed in this conference"), {error, xmpp:err_not_allowed(Txt, Lang)} end. @@ -4279,14 +4410,14 @@ route_invitation(From, Pkt, Invitation, Lang, StateData) -> [io_lib:format( translate:translate( Lang, - <<"~s invites you to the room ~s">>), + ?T("~s invites you to the room ~s")), [jid:encode(From), jid:encode({StateData#state.room, StateData#state.host, <<"">>})]), case (StateData#state.config)#config.password_protected of true -> <<", ", (translate:translate( - Lang, <<"the password is">>))/binary, + Lang, ?T("the password is")))/binary, " '", ((StateData#state.config)#config.password)/binary, "'">>; @@ -4322,8 +4453,8 @@ handle_roommessage_from_nonparticipant(Packet, StateData, From) -> ejabberd_router:route( xmpp:set_from_to(NewPacket, StateData#state.jid, To)); _ -> - ErrText = <<"Only occupants are allowed to send messages " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)), ejabberd_router:route_error(Packet, Err) catch _:{xmpp_codec, Why} -> @@ -4383,7 +4514,14 @@ store_room(StateData) -> store_room(StateData, []). store_room(StateData, ChangesHints) -> % Let store persistent rooms or on those backends that have get_subscribed_rooms - erlang:put(muc_subscribers, StateData#state.subscribers), + Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc), + HasGSR = erlang:function_exported(Mod, get_subscribed_rooms, 3), + case HasGSR of + true -> + ok; + _ -> + erlang:put(muc_subscribers, StateData#state.subscribers) + end, ShouldStore = case (StateData#state.config)#config.persistent of true -> true; @@ -4392,8 +4530,7 @@ store_room(StateData, ChangesHints) -> [] -> false; _ -> - Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc), - erlang:function_exported(Mod, get_subscribed_rooms, 3) + HasGSR end end, if ShouldStore -> @@ -4454,7 +4591,7 @@ send_wrapped(From, To, Packet, Node, State) -> case lists:member(Node, Nodes) of true -> MamEnabled = (State#state.config)#config.mam, - Id = case xmpp:get_subtag(Packet, #stanza_id{}) of + Id = case xmpp:get_subtag(Packet, #stanza_id{by = #jid{}}) of #stanza_id{id = Id2} -> Id2; _ -> @@ -4508,7 +4645,7 @@ wrap(From, To, Packet, Node, Id) -> id = Id, sub_els = [El]}]}}]}. --spec send_wrapped_multiple(jid(), map(), stanza(), binary(), state()) -> ok. +-spec send_wrapped_multiple(jid(), users(), stanza(), binary(), state()) -> ok. send_wrapped_multiple(From, Users, Packet, Node, State) -> maps:fold( fun(_, #user{jid = To}, _) -> |