aboutsummaryrefslogtreecommitdiff
path: root/src/mod_muc_admin.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_muc_admin.erl')
-rw-r--r--src/mod_muc_admin.erl927
1 files changed, 617 insertions, 310 deletions
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index 76e90957e..2163b5e3e 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -3,7 +3,24 @@
%%% Author : Badlop <badlop@ono.com>
%%% Purpose : Tools for additional MUC administration
%%% Created : 8 Sep 2007 by Badlop <badlop@ono.com>
-%%% Id : $Id: mod_muc_admin.erl 1133 2012-10-17 22:13:06Z badlop $
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
%%%----------------------------------------------------------------------
-module(mod_muc_admin).
@@ -11,26 +28,29 @@
-behaviour(gen_mod).
--export([start/2, stop/1, depends/2, muc_online_rooms/1,
- muc_unregister_nick/1, create_room/3, destroy_room/2,
+-export([start/2, stop/1, reload/3, depends/2,
+ muc_online_rooms/1, muc_online_rooms_by_regex/2,
+ muc_register_nick/3, muc_unregister_nick/2,
+ create_room_with_opts/4, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
+ rooms_empty_list/1, rooms_empty_destroy/1,
get_user_rooms/2, get_room_occupants/2,
get_room_occupants_number/2, send_direct_invitation/5,
change_room_option/4, get_room_options/2,
- set_room_affiliation/4, get_room_affiliations/2,
+ set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
web_menu_main/2, web_page_main/2, web_menu_host/3,
- subscribe_room/4, unsubscribe_room/2,
- web_page_host/3, mod_opt_type/1, get_commands_spec/0]).
+ subscribe_room/4, unsubscribe_room/2, get_subscribers/2,
+ web_page_host/3, mod_options/1, get_commands_spec/0, find_hosts/1]).
--include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
--include("mod_muc_room.hrl").
+-include("xmpp.hrl").
-include("mod_muc.hrl").
+-include("mod_muc_room.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_commands.hrl").
+-include("translate.hrl").
%%----------------------------
%% gen_mod
@@ -44,12 +64,20 @@ start(Host, _Opts) ->
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
stop(Host) ->
- ejabberd_commands:unregister_commands(get_commands_spec()),
+ case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
+ false ->
+ ejabberd_commands:unregister_commands(get_commands_spec());
+ true ->
+ ok
+ end,
ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
+reload(_Host, _NewOpts, _OldOpts) ->
+ ok.
+
depends(_Host, _Opts) ->
[{mod_muc, hard}].
@@ -63,57 +91,158 @@ get_commands_spec() ->
desc = "List existing rooms ('global' to get all vhosts)",
policy = admin,
module = ?MODULE, function = muc_online_rooms,
- args = [{host, binary}],
+ args_desc = ["MUC service, or 'global' for all"],
+ args_example = ["muc.example.com"],
+ result_desc = "List of rooms",
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
+ args = [{service, binary}],
+ args_rename = [{host, service}],
result = {rooms, {list, {room, string}}}},
+ #ejabberd_commands{name = muc_online_rooms_by_regex, tags = [muc],
+ desc = "List existing rooms ('global' to get all vhosts) by regex",
+ policy = admin,
+ module = ?MODULE, function = muc_online_rooms_by_regex,
+ args_desc = ["MUC service, or 'global' for all",
+ "Regex pattern for room name"],
+ args_example = ["muc.example.com", "^prefix"],
+ result_desc = "List of rooms with summary",
+ result_example = [{"room1@muc.example.com", "true", 10},
+ {"room2@muc.example.com", "false", 10}],
+ args = [{service, binary}, {regex, binary}],
+ args_rename = [{host, service}],
+ result = {rooms, {list, {room, {tuple,
+ [{jid, string},
+ {public, string},
+ {participants, integer}
+ ]}}}}},
+ #ejabberd_commands{name = muc_register_nick, tags = [muc],
+ desc = "Register a nick to a User JID in a MUC service",
+ module = ?MODULE, function = muc_register_nick,
+ args_desc = ["Nick", "User JID", "Service"],
+ args_example = [<<"Tim">>, <<"tim@example.org">>, <<"muc.example.org">>],
+ args = [{nick, binary}, {jid, binary}, {service, binary}],
+ args_rename = [{host, service}],
+ result = {res, rescode}},
#ejabberd_commands{name = muc_unregister_nick, tags = [muc],
- desc = "Unregister the nick in the MUC service",
+ desc = "Unregister the nick registered by that account in the MUC service",
module = ?MODULE, function = muc_unregister_nick,
- args = [{nick, binary}],
+ args_desc = ["User JID", "MUC service"],
+ args_example = [<<"tim@example.org">>, <<"muc.example.org">>],
+ args = [{jid, binary}, {service, binary}],
+ args_rename = [{host, service}],
result = {res, rescode}},
#ejabberd_commands{name = create_room, tags = [muc_room],
desc = "Create a MUC room name@service in host",
module = ?MODULE, function = create_room,
+ args_desc = ["Room name", "MUC service", "Server host"],
+ args_example = ["room1", "muc.example.com", "example.com"],
args = [{name, binary}, {service, binary},
{host, binary}],
result = {res, rescode}},
#ejabberd_commands{name = destroy_room, tags = [muc_room],
desc = "Destroy a MUC room",
module = ?MODULE, function = destroy_room,
+ args_desc = ["Room name", "MUC service"],
+ args_example = ["room1", "muc.example.com"],
args = [{name, binary}, {service, binary}],
result = {res, rescode}},
#ejabberd_commands{name = create_rooms_file, tags = [muc],
desc = "Create the rooms indicated in file",
longdesc = "Provide one room JID per line. Rooms will be created after restart.",
module = ?MODULE, function = create_rooms_file,
+ args_desc = ["Path to the text file with one room JID per line"],
+ args_example = ["/home/ejabberd/rooms.txt"],
args = [{file, string}],
result = {res, rescode}},
+ #ejabberd_commands{name = create_room_with_opts, tags = [muc_room],
+ desc = "Create a MUC room name@service in host with given options",
+ module = ?MODULE, function = create_room_with_opts,
+ args_desc = ["Room name", "MUC service", "Server host", "List of options"],
+ args_example = ["room1", "muc.example.com", "localhost", [{"members_only","true"}]],
+ args = [{name, binary}, {service, binary},
+ {host, binary},
+ {options, {list,
+ {option, {tuple,
+ [{name, binary},
+ {value, binary}
+ ]}}
+ }}],
+ result = {res, rescode}},
#ejabberd_commands{name = destroy_rooms_file, tags = [muc],
desc = "Destroy the rooms indicated in file",
longdesc = "Provide one room JID per line.",
module = ?MODULE, function = destroy_rooms_file,
+ args_desc = ["Path to the text file with one room JID per line"],
+ args_example = ["/home/ejabberd/rooms.txt"],
args = [{file, string}],
result = {res, rescode}},
#ejabberd_commands{name = rooms_unused_list, tags = [muc],
- desc = "List the rooms that are unused for many days in host",
+ desc = "List the rooms that are unused for many days in the service",
+ longdesc = "The room recent history is used, so it's recommended "
+ " to wait a few days after service start before running this."
+ " The MUC service argument can be 'global' to get all hosts.",
module = ?MODULE, function = rooms_unused_list,
- args = [{host, binary}, {days, integer}],
+ args_desc = ["MUC service, or 'global' for all", "Number of days"],
+ args_example = ["muc.example.com", 31],
+ result_desc = "List of unused rooms",
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
+ args = [{service, binary}, {days, integer}],
+ args_rename = [{host, service}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
- desc = "Destroy the rooms that are unused for many days in host",
+ desc = "Destroy the rooms that are unused for many days in the service",
+ longdesc = "The room recent history is used, so it's recommended "
+ " to wait a few days after service start before running this."
+ " The MUC service argument can be 'global' to get all hosts.",
module = ?MODULE, function = rooms_unused_destroy,
- args = [{host, binary}, {days, integer}],
+ args_desc = ["MUC service, or 'global' for all", "Number of days"],
+ args_example = ["muc.example.com", 31],
+ result_desc = "List of unused rooms that has been destroyed",
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
+ args = [{service, binary}, {days, integer}],
+ args_rename = [{host, service}],
+ result = {rooms, {list, {room, string}}}},
+
+ #ejabberd_commands{name = rooms_empty_list, tags = [muc],
+ desc = "List the rooms that have no messages in archive",
+ longdesc = "The MUC service argument can be 'global' to get all hosts.",
+ module = ?MODULE, function = rooms_empty_list,
+ args_desc = ["MUC service, or 'global' for all"],
+ args_example = ["muc.example.com"],
+ result_desc = "List of empty rooms",
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
+ args = [{service, binary}],
+ args_rename = [{host, service}],
+ result = {rooms, {list, {room, string}}}},
+ #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
+ desc = "Destroy the rooms that have no messages in archive",
+ longdesc = "The MUC service argument can be 'global' to get all hosts.",
+ module = ?MODULE, function = rooms_empty_destroy,
+ args_desc = ["MUC service, or 'global' for all"],
+ args_example = ["muc.example.com"],
+ result_desc = "List of empty rooms that have been destroyed",
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
+ args = [{service, binary}],
+ args_rename = [{host, service}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = get_user_rooms, tags = [muc],
desc = "Get the list of rooms where this user is occupant",
module = ?MODULE, function = get_user_rooms,
+ args_desc = ["Username", "Server host"],
+ args_example = ["tom", "example.com"],
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
args = [{user, binary}, {host, binary}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = get_room_occupants, tags = [muc_room],
desc = "Get the list of occupants of a MUC room",
module = ?MODULE, function = get_room_occupants,
+ args_desc = ["Room name", "MUC service"],
+ args_example = ["room1", "muc.example.com"],
+ result_desc = "The list of occupants with JID, nick and affiliation",
+ result_example = [{"user1@example.com/psi", "User 1", "owner"}],
args = [{name, binary}, {service, binary}],
result = {occupants, {list,
{occupant, {tuple,
@@ -126,6 +255,10 @@ get_commands_spec() ->
#ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
desc = "Get the number of occupants of a MUC room",
module = ?MODULE, function = get_room_occupants_number,
+ args_desc = ["Room name", "MUC service"],
+ args_example = ["room1", "muc.example.com"],
+ result_desc = "Number of room occupants",
+ result_example = 7,
args = [{name, binary}, {service, binary}],
result = {occupants, integer}},
@@ -133,18 +266,30 @@ get_commands_spec() ->
desc = "Send a direct invitation to several destinations",
longdesc = "Password and Message can also be: none. Users JIDs are separated with : ",
module = ?MODULE, function = send_direct_invitation,
- args = [{name, binary}, {service, binary}, {password, binary}, {reason, binary}, {users, binary}],
+ args_desc = ["Room name", "MUC service", "Password, or none",
+ "Reason text, or none", "Users JIDs separated with : characters"],
+ args_example = [<<"room1">>, <<"muc.example.com">>,
+ <<>>, <<"Check this out!">>,
+ "user2@localhost:user3@example.com"],
+ args = [{name, binary}, {service, binary}, {password, binary},
+ {reason, binary}, {users, binary}],
result = {res, rescode}},
#ejabberd_commands{name = change_room_option, tags = [muc_room],
desc = "Change an option in a MUC room",
module = ?MODULE, function = change_room_option,
+ args_desc = ["Room name", "MUC service", "Option name", "Value to assign"],
+ args_example = ["room1", "muc.example.com", "members_only", "true"],
args = [{name, binary}, {service, binary},
{option, binary}, {value, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_room_options, tags = [muc_room],
desc = "Get options from a MUC room",
module = ?MODULE, function = get_room_options,
+ args_desc = ["Room name", "MUC service"],
+ args_example = ["room1", "muc.example.com"],
+ result_desc = "List of room options tuples with name and value",
+ result_example = [{"members_only", "true"}],
args = [{name, binary}, {service, binary}],
result = {options, {list,
{option, {tuple,
@@ -155,23 +300,47 @@ get_commands_spec() ->
#ejabberd_commands{name = subscribe_room, tags = [muc_room],
desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room,
+ args_desc = ["User JID", "a user's nick",
+ "the room to subscribe", "nodes separated by commas: ,"],
+ args_example = ["tom@localhost", "Tom", "room1@conference.localhost",
+ "urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
+ result_desc = "The list of nodes that has subscribed",
+ result_example = ["urn:xmpp:mucsub:nodes:messages",
+ "urn:xmpp:mucsub:nodes:affiliations"],
args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, binary}],
result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room],
desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room,
+ args_desc = ["User JID", "the room to subscribe"],
+ args_example = ["tom@localhost", "room1@conference.localhost"],
args = [{user, binary}, {room, binary}],
result = {res, rescode}},
+ #ejabberd_commands{name = get_subscribers, tags = [muc_room],
+ desc = "List subscribers of a MUC conference",
+ module = ?MODULE, function = get_subscribers,
+ args_desc = ["Room name", "MUC service"],
+ args_example = ["room1", "muc.example.com"],
+ result_desc = "The list of users that are subscribed to that room",
+ result_example = ["user2@example.com", "user3@example.com"],
+ args = [{name, binary}, {service, binary}],
+ result = {subscribers, {list, {jid, string}}}},
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
desc = "Change an affiliation in a MUC room",
module = ?MODULE, function = set_room_affiliation,
+ args_desc = ["Room name", "MUC service", "User JID", "Affiliation to set"],
+ args_example = ["room1", "muc.example.com", "user2@example.com", "member"],
args = [{name, binary}, {service, binary},
{jid, binary}, {affiliation, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
desc = "Get the list of affiliations of a MUC room",
module = ?MODULE, function = get_room_affiliations,
+ args_desc = ["Room name", "MUC service"],
+ args_example = ["room1", "muc.example.com"],
+ result_desc = "The list of affiliations with username, domain, affiliation and reason",
+ result_example = [{"user1", "example.com", member, "member"}],
args = [{name, binary}, {service, binary}],
result = {affiliations, {list,
{affiliation, {tuple,
@@ -180,53 +349,82 @@ get_commands_spec() ->
{affiliation, atom},
{reason, string}
]}}
- }}}
- ].
+ }}},
+ #ejabberd_commands{name = get_room_affiliation, tags = [muc_room],
+ desc = "Get affiliation of a user in MUC room",
+ module = ?MODULE, function = get_room_affiliation,
+ args_desc = ["Room name", "MUC service", "User JID"],
+ args_example = ["room1", "muc.example.com", "user1@example.com"],
+ result_desc = "Affiliation of the user",
+ result_example = member,
+ args = [{name, binary}, {service, binary}, {jid, binary}],
+ result = {affiliation, atom}}
+ ].
%%%
%%% ejabberd commands
%%%
-muc_online_rooms(ServerHost) ->
- MUCHost = find_host(ServerHost),
- Rooms = ets:tab2list(muc_online_room),
- lists:foldl(
- fun(Room, Results) ->
- {Roomname, Host} = Room#muc_online_room.name_host,
- case MUCHost of
- global ->
- [<<Roomname/binary, "@", Host/binary>> | Results];
- Host ->
- [<<Roomname/binary, "@", Host/binary>> | Results];
- _ ->
- Results
- end
- end,
- [],
- Rooms).
-
-muc_unregister_nick(Nick) ->
- F2 = fun(N) ->
- [{_,Key,_}] = mnesia:index_read(muc_registered, N, 3),
- mnesia:delete({muc_registered, Key})
- end,
- case mnesia:transaction(F2, [Nick], 1) of
- {atomic, ok} ->
- ok;
- {aborted, _Error} ->
- error
+muc_online_rooms(ServiceArg) ->
+ Hosts = find_services(ServiceArg),
+ lists:flatmap(
+ fun(Host) ->
+ [<<Name/binary, "@", Host/binary>>
+ || {Name, _, _} <- mod_muc:get_online_rooms(Host)]
+ end, Hosts).
+
+muc_online_rooms_by_regex(ServiceArg, Regex) ->
+ {_, P} = re:compile(Regex),
+ Hosts = find_services(ServiceArg),
+ lists:flatmap(
+ fun(Host) ->
+ [build_summary_room(Name, RoomHost, Pid)
+ || {Name, RoomHost, Pid} <- mod_muc:get_online_rooms(Host),
+ is_name_match(Name, P)]
+ end, Hosts).
+
+is_name_match(Name, P) ->
+ case re:run(Name, P) of
+ {match, _} -> true;
+ nomatch -> false
+ end.
+
+build_summary_room(Name, Host, Pid) ->
+ C = get_room_config(Pid),
+ Public = C#config.public,
+ S = get_room_state(Pid),
+ Participants = maps:size(S#state.users),
+ {<<Name/binary, "@", Host/binary>>,
+ misc:atom_to_binary(Public),
+ Participants
+ }.
+
+muc_register_nick(Nick, FromBinary, Service) ->
+ ServerHost = get_room_serverhost(Service),
+ From = jid:decode(FromBinary),
+ Lang = <<"en">>,
+ case mod_muc:iq_set_register_info(ServerHost, Service, From, Nick, Lang) of
+ {result, undefined} -> ok;
+ E -> E
end.
-get_user_rooms(LUser, LServer) ->
- US = {LUser, LServer},
- case catch ets:select(muc_online_users,
- [{#muc_online_users{us = US, room='$1', host='$2', _ = '_'}, [], [{{'$1', '$2'}}]}])
- of
- Res when is_list(Res) ->
- [<<R/binary, "@", H/binary>> || {R, H} <- Res];
- _ -> []
- end.
+muc_unregister_nick(FromBinary, Service) ->
+ muc_register_nick(<<"">>, FromBinary, Service).
+
+get_user_rooms(User, Server) ->
+ lists:flatmap(
+ fun(ServerHost) ->
+ case gen_mod:is_loaded(ServerHost, mod_muc) of
+ true ->
+ Rooms = mod_muc:get_online_rooms_by_user(
+ ServerHost, jid:nodeprep(User), jid:nodeprep(Server)),
+ [<<Name/binary, "@", Host/binary>>
+ || {Name, Host} <- Rooms];
+ false ->
+ []
+ end
+ end, ejabberd_option:hosts()).
%%----------------------------
%% Ad-hoc commands
@@ -241,10 +439,10 @@ get_user_rooms(LUser, LServer) ->
%% Web Admin Menu
web_menu_main(Acc, Lang) ->
- Acc ++ [{<<"muc">>, ?T(<<"Multi-User Chat">>)}].
+ Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}].
web_menu_host(Acc, _Host, Lang) ->
- Acc ++ [{<<"muc">>, ?T(<<"Multi-User Chat">>)}].
+ Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}].
%%---------------
@@ -252,19 +450,22 @@ web_menu_host(Acc, _Host, Lang) ->
-define(TDTD(L, N),
?XE(<<"tr">>, [?XCT(<<"td">>, L),
- ?XC(<<"td">>, jlib:integer_to_binary(N))
+ ?XC(<<"td">>, integer_to_binary(N))
])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
- Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>),
- ?XCT(<<"h3">>, <<"Statistics">>),
+ OnlineRoomsNumber = lists:foldl(
+ fun(Host, Acc) ->
+ Acc + mod_muc:count_online_rooms(Host)
+ end, 0, find_hosts(global)),
+ PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
+ Res = ?H1GL(PageTitle, <<"mod-muc">>, <<"mod_muc">>) ++
+ [?XCT(<<"h3">>, ?T("Statistics")),
?XAE(<<"table">>, [],
- [?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, ets:info(muc_online_room, size)),
- ?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)),
- ?TDTD(<<"Registered nicknames">>, mnesia:table_info(muc_registered, size))
+ [?XE(<<"tbody">>, [?TDTD(?T("Total rooms"), OnlineRoomsNumber)
])
]),
- ?XE(<<"ul">>, [?LI([?ACT(<<"rooms">>, <<"List of rooms">>)])])
+ ?XE(<<"ul">>, [?LI([?ACT(<<"rooms/">>, ?T("List of rooms"))])])
],
{stop, Res};
@@ -280,7 +481,7 @@ web_page_host(_, Host,
q = Q,
lang = Lang} = _Request) ->
Sort_query = get_sort_query(Q),
- Res = make_rooms_page(find_host(Host), Lang, Sort_query),
+ Res = make_rooms_page(Host, Lang, Sort_query),
{stop, Res};
web_page_host(Acc, _, _) -> Acc.
@@ -294,14 +495,15 @@ get_sort_query(Q) ->
get_sort_query2(Q) ->
{value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
- Integer = jlib:binary_to_integer(String),
+ Integer = binary_to_integer(String),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
end.
make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
- Rooms_names = get_rooms(Host),
+ Service = find_service(Host),
+ Rooms_names = get_rooms(Service),
Rooms_infos = build_info_rooms(Rooms_names),
Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
@@ -320,7 +522,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
- NCS = jlib:integer_to_binary(Num_column),
+ NCS = integer_to_binary(Num_column),
TD = ?XE(<<"td">>, [?CT(Title),
?C(<<" ">>),
?AC(<<"?sort=", NCS/binary>>, <<"<">>),
@@ -330,8 +532,9 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
end,
1,
Titles),
- [?XCT(<<"h1">>, <<"Multi-User Chat">>),
- ?XCT(<<"h2">>, <<"Chatrooms">>),
+ PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
+ ?H1GL(PageTitle, <<"mod-muc">>, <<"mod_muc">>) ++
+ [?XCT(<<"h2">>, ?T("Chatrooms")),
?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>, Titles_TR)]
@@ -351,7 +554,7 @@ sort_rooms(Direction, Column, Rooms) ->
build_info_rooms(Rooms) ->
[build_info_room(Room) || Room <- Rooms].
-build_info_room({Name, Host, Pid}) ->
+build_info_room({Name, Host, _ServerHost, Pid}) ->
C = get_room_config(Pid),
Title = C#config.title,
Public = C#config.public,
@@ -360,17 +563,17 @@ build_info_room({Name, Host, Pid}) ->
S = get_room_state(Pid),
Just_created = S#state.just_created,
- Num_participants = length(dict:fetch_keys(S#state.users)),
+ Num_participants = maps:size(S#state.users),
History = (S#state.history)#lqueue.queue,
Ts_last_message =
- case queue:is_empty(History) of
+ case p1_queue:is_empty(History) of
true ->
<<"A long time ago">>;
false ->
- Last_message1 = queue:last(History),
+ Last_message1 = get_queue_last(History),
{_, _, _, Ts_last, _} = Last_message1,
- jlib:timestamp_to_legacy(Ts_last)
+ xmpp_util:encode_timestamp(Ts_last)
end,
{<<Name/binary, "@", Host/binary>>,
@@ -382,6 +585,10 @@ build_info_room({Name, Host, Pid}) ->
Just_created,
Title}.
+get_queue_last(Queue) ->
+ List = p1_queue:to_list(Queue),
+ lists:last(List).
+
prepare_rooms_infos(Rooms) ->
[prepare_room_info(Room) || Room <- Rooms].
prepare_room_info(Room_info) ->
@@ -394,14 +601,21 @@ prepare_room_info(Room_info) ->
Just_created,
Title} = Room_info,
[NameHost,
- jlib:integer_to_binary(Num_participants),
+ integer_to_binary(Num_participants),
Ts_last_message,
- jlib:atom_to_binary(Public),
- jlib:atom_to_binary(Persistent),
- jlib:atom_to_binary(Logging),
- jlib:atom_to_binary(Just_created),
+ misc:atom_to_binary(Public),
+ misc:atom_to_binary(Persistent),
+ misc:atom_to_binary(Logging),
+ justcreated_to_binary(Just_created),
Title].
+justcreated_to_binary(J) when is_integer(J) ->
+ JNow = misc:usec_to_now(J),
+ {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(JNow),
+ str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day, Hour, Minute, Second]);
+justcreated_to_binary(J) when is_atom(J) ->
+ misc:atom_to_binary(J).
%%----------------------------
%% Create/Delete Room
@@ -411,53 +625,59 @@ prepare_room_info(Room_info) ->
%% ok | error
%% @doc Create a room immediately with the default options.
create_room(Name1, Host1, ServerHost) ->
- Name = jid:nodeprep(Name1),
- Host = jid:nodeprep(Host1),
+ case create_room_with_opts(Name1, Host1, ServerHost, []) of
+ ok -> change_room_option(Name1, Host1, <<"persistent">>, <<"true">>);
+ Error -> Error
+ end.
+
+create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
+ true = (error /= (Name = jid:nodeprep(Name1))),
+ true = (error /= (Host = jid:nodeprep(Host1))),
%% Get the default room options from the muc configuration
- DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
- default_room_options, fun(X) -> X end, []),
+ DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
+ %% Change default room options as required
+ FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
+ RoomOpts = lists:ukeymerge(1,
+ lists:keysort(1, FormattedRoomOpts),
+ lists:keysort(1, DefRoomOpts)),
%% Store the room on the server, it is not started yet though at this point
- mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts),
+ mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
%% Get all remaining mod_muc parameters that might be utilized
- Access = gen_mod:get_module_opt(ServerHost, mod_muc, access, fun(X) -> X end, all),
- AcCreate = gen_mod:get_module_opt(ServerHost, mod_muc, access_create, fun(X) -> X end, all),
- AcAdmin = gen_mod:get_module_opt(ServerHost, mod_muc, access_admin, fun(X) -> X end, none),
- AcPer = gen_mod:get_module_opt(ServerHost, mod_muc, access_persistent, fun(X) -> X end, all),
- HistorySize = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, fun(X) -> X end, 20),
- RoomShaper = gen_mod:get_module_opt(ServerHost, mod_muc, room_shaper, fun(X) -> X end, none),
+ Access = mod_muc_opt:access(ServerHost),
+ AcCreate = mod_muc_opt:access_create(ServerHost),
+ AcAdmin = mod_muc_opt:access_admin(ServerHost),
+ AcPer = mod_muc_opt:access_persistent(ServerHost),
+ AcMam = mod_muc_opt:access_mam(ServerHost),
+ HistorySize = mod_muc_opt:history_size(ServerHost),
+ RoomShaper = mod_muc_opt:room_shaper(ServerHost),
+ QueueType = mod_muc_opt:queue_type(ServerHost),
%% If the room does not exist yet in the muc_online_room
- case mnesia:dirty_read(muc_online_room, {Name, Host}) of
- [] ->
+ case mod_muc:find_online_room(Name, Host) of
+ error ->
%% Start the room
{ok, Pid} = mod_muc_room:start(
Host,
ServerHost,
- {Access, AcCreate, AcAdmin, AcPer},
+ {Access, AcCreate, AcAdmin, AcPer, AcMam},
Name,
HistorySize,
RoomShaper,
- DefRoomOpts),
- {atomic, ok} = register_room(Host, Name, Pid),
+ RoomOpts,
+ QueueType),
+ mod_muc:register_online_room(Name, Host, Pid),
ok;
- _ ->
+ {ok, _} ->
error
end.
-register_room(Host, Name, Pid) ->
- F = fun() ->
- mnesia:write(#muc_online_room{name_host = {Name, Host},
- pid = Pid})
- end,
- mnesia:transaction(F).
-
%% Create the room only in the database.
%% It is required to restart the MUC service for the room to appear.
muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
- io:format("Creating room ~s@~s~n", [Name, Host]),
+ io:format("Creating room ~ts@~ts~n", [Name, Host]),
mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts).
%% @spec (Name::binary(), Host::binary()) ->
@@ -466,17 +686,15 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
%% If the room has participants, they are not notified that the room was destroyed;
%% they will notice when they try to chat and receive an error that the room doesn't exist.
destroy_room(Name, Service) ->
- case mnesia:dirty_read(muc_online_room, {Name, Service}) of
- [R] ->
- Pid = R#muc_online_room.pid,
- gen_fsm:send_all_state_event(Pid, destroy),
- ok;
- [] ->
+ case mod_muc:find_online_room(Name, Service) of
+ {ok, Pid} ->
+ mod_muc_room:destroy(Pid);
+ error ->
error
end.
destroy_room({N, H, SH}) ->
- io:format("Destroying room: ~s@~s - vhost: ~s~n", [N, H, SH]),
+ io:format("Destroying room: ~ts@~ts - vhost: ~ts~n", [N, H, SH]),
destroy_room(N, H).
@@ -488,7 +706,7 @@ destroy_room({N, H, SH}) ->
%% The file encoding must be UTF-8
destroy_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read, binary]),
+ {ok, F} = file:open(Filename, [read]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
@@ -497,7 +715,9 @@ destroy_rooms_file(Filename) ->
read_rooms(_F, eof, L) ->
L;
-
+read_rooms(F, no_room, L) ->
+ RJID2 = read_room(F),
+ read_rooms(F, RJID2, L);
read_rooms(F, RJID, L) ->
RJID2 = read_room(F),
read_rooms(F, RJID2, [RJID | L]).
@@ -506,8 +726,8 @@ read_room(F) ->
case io:get_line(F, "") of
eof -> eof;
String ->
- case io_lib:fread("~s", String) of
- {ok, [RoomJID], _} -> split_roomjid(RoomJID);
+ case io_lib:fread("~ts", String) of
+ {ok, [RoomJID], _} -> split_roomjid(list_to_binary(RoomJID));
{error, What} ->
io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
end
@@ -516,96 +736,93 @@ read_room(F) ->
%% This function is quite rudimentary
%% and may not be accurate
split_roomjid(RoomJID) ->
- [Name, Host] = binary:split(RoomJID, <<"@">>),
+ split_roomjid2(binary:split(RoomJID, <<"@">>)).
+split_roomjid2([Name, Host]) ->
[_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
- {Name, Host, ServerHost}.
+ {Name, Host, ServerHost};
+split_roomjid2(_) ->
+ no_room.
%%----------------------------
%% Create Rooms in File
%%----------------------------
create_rooms_file(Filename) ->
- {ok, F} = file:open(Filename, [read, binary]),
+ {ok, F} = file:open(Filename, [read]),
RJID = read_room(F),
Rooms = read_rooms(F, RJID, []),
file:close(F),
%% Read the default room options defined for the first virtual host
- DefRoomOpts = gen_mod:get_module_opt(?MYNAME, mod_muc,
- default_room_options,
- fun(L) when is_list(L) -> L end, []),
- [muc_create_room(?MYNAME, A, DefRoomOpts) || A <- Rooms],
+ DefRoomOpts = mod_muc_opt:default_room_options(ejabberd_config:get_myname()),
+ [muc_create_room(ejabberd_config:get_myname(), A, DefRoomOpts) || A <- Rooms],
ok.
-%%----------------------------
-%% List/Delete Unused Rooms
-%%----------------------------
+%%---------------------------------
+%% List/Delete Unused/Empty Rooms
+%%---------------------------------
%%---------------
%% Control
-rooms_unused_list(Host, Days) ->
- rooms_unused_report(list, Host, Days).
-rooms_unused_destroy(Host, Days) ->
- rooms_unused_report(destroy, Host, Days).
+rooms_unused_list(Service, Days) ->
+ rooms_report(unused, list, Service, Days).
+rooms_unused_destroy(Service, Days) ->
+ rooms_report(unused, destroy, Service, Days).
+
+rooms_empty_list(Service) ->
+ rooms_report(empty, list, Service, 0).
+rooms_empty_destroy(Service) ->
+ rooms_report(empty, destroy, Service, 0).
-rooms_unused_report(Action, Host, Days) ->
- {NA, NP, RP} = muc_unused(Action, Host, Days),
- io:format("Unused rooms: ~p out of ~p~n", [NP, NA]),
- [<<R/binary, "@", H/binary>> || {R, H, _P} <- RP].
-muc_unused(Action, ServerHost, Days) ->
- Host = find_host(ServerHost),
- muc_unused2(Action, ServerHost, Host, Days).
+rooms_report(Method, Action, Service, Days) ->
+ {NA, NP, RP} = muc_unused(Method, Action, Service, Days),
+ io:format("rooms ~ts: ~p out of ~p~n", [Method, NP, NA]),
+ [<<R/binary, "@", H/binary>> || {R, H, _SH, _P} <- RP].
-muc_unused2(Action, ServerHost, Host, Last_allowed) ->
+muc_unused(Method, Action, Service, Last_allowed) ->
%% Get all required info about all existing rooms
- Rooms_all = get_rooms(Host),
+ Rooms_all = get_rooms(Service),
%% Decide which ones pass the requirements
- Rooms_pass = decide_rooms(Rooms_all, Last_allowed),
+ Rooms_pass = decide_rooms(Method, Rooms_all, Last_allowed),
Num_rooms_all = length(Rooms_all),
Num_rooms_pass = length(Rooms_pass),
%% Perform the desired action for matching rooms
- act_on_rooms(Action, Rooms_pass, ServerHost),
+ act_on_rooms(Method, Action, Rooms_pass),
{Num_rooms_all, Num_rooms_pass, Rooms_pass}.
%%---------------
%% Get info
-get_rooms(Host) ->
- Get_room_names = fun(Room_reg, Names) ->
- Pid = Room_reg#muc_online_room.pid,
- case {Host, Room_reg#muc_online_room.name_host} of
- {Host, {Name1, Host}} ->
- [{Name1, Host, Pid} | Names];
- {global, {Name1, Host1}} ->
- [{Name1, Host1, Pid} | Names];
- _ ->
- Names
- end
- end,
- ets:foldr(Get_room_names, [], muc_online_room).
+get_rooms(ServiceArg) ->
+ Hosts = find_services(ServiceArg),
+ lists:flatmap(
+ fun(Host) ->
+ [{RoomName, RoomHost, Host, Pid}
+ || {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
+ end, Hosts).
get_room_config(Room_pid) ->
- {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
+ {ok, R} = mod_muc_room:get_config(Room_pid),
R.
get_room_state(Room_pid) ->
- {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
+ {ok, R} = mod_muc_room:get_state(Room_pid),
R.
%%---------------
%% Decide
-decide_rooms(Rooms, Last_allowed) ->
- Decide = fun(R) -> decide_room(R, Last_allowed) end,
+decide_rooms(Method, Rooms, Last_allowed) ->
+ Decide = fun(R) -> decide_room(Method, R, Last_allowed) end,
lists:filter(Decide, Rooms).
-decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
+decide_room(unused, {_Room_name, _Host, ServerHost, Room_pid}, Last_allowed) ->
C = get_room_config(Room_pid),
Persistent = C#config.persistent,
@@ -613,29 +830,46 @@ decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
Just_created = S#state.just_created,
Room_users = S#state.users,
- Num_users = length(?DICT:to_list(Room_users)),
+ Num_users = maps:size(Room_users),
History = (S#state.history)#lqueue.queue,
Ts_now = calendar:universal_time(),
- Ts_uptime = uptime_seconds(),
- {Has_hist, Last} = case queue:is_empty(History) of
+ HistorySize = mod_muc_opt:history_size(ServerHost),
+ {Has_hist, Last} = case p1_queue:is_empty(History) of
+ true when (HistorySize == 0) or (Just_created == true) ->
+ {false, 0};
true ->
- {false, Ts_uptime};
+ Ts_diff = (erlang:system_time(microsecond)
+ - Just_created) div 1000000,
+ {false, Ts_diff};
false ->
- Last_message = queue:last(History),
- {_, _, _, Ts_last, _} = Last_message,
+ Last_message = get_queue_last(History),
+ Ts_last = calendar:now_to_universal_time(
+ element(4, Last_message)),
Ts_diff =
calendar:datetime_to_gregorian_seconds(Ts_now)
- calendar:datetime_to_gregorian_seconds(Ts_last),
{true, Ts_diff}
end,
-
case {Persistent, Just_created, Num_users, Has_hist, seconds_to_days(Last)} of
- {_true, false, 0, _, Last_days}
- when Last_days >= Last_allowed ->
+ {_true, JC, 0, _, Last_days}
+ when (Last_days >= Last_allowed) and (JC /= true) ->
true;
_ ->
false
+ end;
+decide_room(empty, {Room_name, Host, ServerHost, _Room_pid}, _Last_allowed) ->
+ case gen_mod:is_loaded(ServerHost, mod_mam) of
+ true ->
+ Room_options = get_room_options(Room_name, Host),
+ case lists:keyfind(<<"mam">>, 1, Room_options) of
+ {<<"mam">>, <<"true">>} ->
+ mod_mam:is_empty_for_room(ServerHost, Room_name, Host);
+ _ ->
+ false
+ end;
+ _ ->
+ false
end.
seconds_to_days(S) ->
@@ -644,29 +878,20 @@ seconds_to_days(S) ->
%%---------------
%% Act
-act_on_rooms(Action, Rooms, ServerHost) ->
- ServerHosts = [ {A, find_host(A)} || A <- ?MYHOSTS ],
- Delete = fun({_N, H, _Pid} = Room) ->
- SH = case ServerHost of
- global -> find_serverhost(H, ServerHosts);
- O -> O
- end,
-
- act_on_room(Action, Room, SH)
+act_on_rooms(Method, Action, Rooms) ->
+ Delete = fun(Room) ->
+ act_on_room(Method, Action, Room)
end,
lists:foreach(Delete, Rooms).
-find_serverhost(Host, ServerHosts) ->
- {value, {ServerHost, Host}} = lists:keysearch(Host, 2, ServerHosts),
- ServerHost.
-
-act_on_room(destroy, {N, H, Pid}, SH) ->
- gen_fsm:send_all_state_event(
- Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
+act_on_room(Method, destroy, {N, H, SH, Pid}) ->
+ Message = iolist_to_binary(io_lib:format(
+ <<"Room destroyed by rooms_~ts_destroy.">>, [Method])),
+ mod_muc_room:destroy(Pid, Message),
mod_muc:room_destroyed(H, N, Pid, SH),
mod_muc:forget_room(SH, H, N);
-act_on_room(list, _, _) ->
+act_on_room(_Method, list, _) ->
ok.
@@ -684,14 +909,20 @@ get_room_occupants(Pid) ->
S = get_room_state(Pid),
lists:map(
fun({_LJID, Info}) ->
- {jid:to_string(Info#user.jid),
+ {jid:encode(Info#user.jid),
Info#user.nick,
atom_to_list(Info#user.role)}
end,
- dict:to_list(S#state.users)).
+ maps:to_list(S#state.users)).
get_room_occupants_number(Room, Host) ->
- length(get_room_occupants(Room, Host)).
+ case get_room_pid(Room, Host) of
+ room_not_found ->
+ throw({error, room_not_found});
+ Pid ->
+ S = get_room_state(Pid),
+ maps:size(S#state.users)
+ end.
%%----------------------------
%% Send Direct Invitation
@@ -699,12 +930,11 @@ get_room_occupants_number(Room, Host) ->
%% http://xmpp.org/extensions/xep-0249.html
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) ->
- RoomJid = jid:make(RoomName, RoomService, <<"">>),
- RoomString = jid:to_string(RoomJid),
- XmlEl = build_invitation(Password, Reason, RoomString),
- UsersStrings = get_users_to_invite(RoomJid, UsersString),
- [send_direct_invitation(RoomJid, UserStrings, XmlEl)
- || UserStrings <- UsersStrings],
+ RoomJid = jid:make(RoomName, RoomService),
+ XmlEl = build_invitation(Password, Reason, RoomJid),
+ Users = get_users_to_invite(RoomJid, UsersString),
+ [send_direct_invitation(RoomJid, UserJid, XmlEl)
+ || UserJid <- Users],
timer:sleep(1000),
ok.
@@ -712,41 +942,38 @@ get_users_to_invite(RoomJid, UsersString) ->
UsersStrings = binary:split(UsersString, <<":">>, [global]),
OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
RoomJid#jid.lserver),
- OccupantsJids = [jid:from_string(JidString)
+ OccupantsJids = [jid:decode(JidString)
|| {JidString, _Nick, _} <- OccupantsTuples],
lists:filtermap(
fun(UserString) ->
- UserJid = jid:from_string(UserString),
+ UserJid = jid:decode(UserString),
Val = lists:all(fun(OccupantJid) ->
UserJid#jid.luser /= OccupantJid#jid.luser
orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
end,
OccupantsJids),
- case Val of
- true -> {true, UserJid};
+ case {UserJid#jid.luser, Val} of
+ {<<>>, _} -> false;
+ {_, true} -> {true, UserJid};
_ -> false
end
end,
UsersStrings).
-build_invitation(Password, Reason, RoomString) ->
- PasswordAttrList = case Password of
- <<"none">> -> [];
- _ -> [{<<"password">>, Password}]
- end,
- ReasonAttrList = case Reason of
- <<"none">> -> [];
- _ -> [{<<"reason">>, Reason}]
- end,
- XAttrs = [{<<"xmlns">>, ?NS_XCONFERENCE},
- {<<"jid">>, RoomString}]
- ++ PasswordAttrList
- ++ ReasonAttrList,
- XEl = {xmlel, <<"x">>, XAttrs, []},
- {xmlel, <<"message">>, [], [XEl]}.
-
-send_direct_invitation(FromJid, UserJid, XmlEl) ->
- ejabberd_router:route(FromJid, UserJid, XmlEl).
+build_invitation(Password, Reason, RoomJid) ->
+ Invite = #x_conference{jid = RoomJid,
+ password = case Password of
+ <<"none">> -> <<>>;
+ _ -> Password
+ end,
+ reason = case Reason of
+ <<"none">> -> <<>>;
+ _ -> Reason
+ end},
+ #message{sub_els = [Invite]}.
+
+send_direct_invitation(FromJid, UserJid, Msg) ->
+ ejabberd_router:route(xmpp:set_from_to(Msg, FromJid, UserJid)).
%%----------------------------
%% Change Room Option
@@ -759,39 +986,50 @@ send_direct_invitation(FromJid, UserJid, XmlEl) ->
%% the option to change (for example title or max_users),
%% and the value to assign to the new option.
%% For example:
-%% change_room_option("testroom", "conference.localhost", "title", "Test Room")
-change_room_option(Name, Service, Option, Value) when is_atom(Option) ->
- Pid = get_room_pid(Name, Service),
- {ok, _} = change_room_option(Pid, Option, Value),
- ok;
+%% change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)
change_room_option(Name, Service, OptionString, ValueString) ->
- Option = jlib:binary_to_atom(OptionString),
+ case get_room_pid(Name, Service) of
+ room_not_found ->
+ room_not_found;
+ Pid ->
+ {Option, Value} = format_room_option(OptionString, ValueString),
+ Config = get_room_config(Pid),
+ Config2 = change_option(Option, Value, Config),
+ {ok, _} = mod_muc_room:set_config(Pid, Config2),
+ ok
+ end.
+
+format_room_option(OptionString, ValueString) ->
+ Option = misc:binary_to_atom(OptionString),
Value = case Option of
title -> ValueString;
description -> ValueString;
password -> ValueString;
subject ->ValueString;
subject_author ->ValueString;
- max_users -> jlib:binary_to_integer(ValueString);
- _ -> jlib:binary_to_atom(ValueString)
+ presence_broadcast ->misc:expr_to_term(ValueString);
+ max_users -> binary_to_integer(ValueString);
+ voice_request_min_interval -> binary_to_integer(ValueString);
+ vcard -> ValueString;
+ vcard_xupdate when ValueString /= <<"undefined">>,
+ ValueString /= <<"external">> ->
+ ValueString;
+ lang -> ValueString;
+ pubsub -> ValueString;
+ _ -> misc:binary_to_atom(ValueString)
end,
- change_room_option(Name, Service, Option, Value).
-
-change_room_option(Pid, Option, Value) ->
- Config = get_room_config(Pid),
- Config2 = change_option(Option, Value, Config),
- gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}).
+ {Option, Value}.
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
get_room_pid(Name, Service) ->
- case mnesia:dirty_read(muc_online_room, {Name, Service}) of
- [] ->
+ case mod_muc:find_online_room(Name, Service) of
+ error ->
room_not_found;
- [Room] ->
- Room#muc_online_room.pid
+ {ok, Pid} ->
+ Pid
end.
-%% It is required to put explicitely all the options because
+%% It is required to put explicitly all the options because
%% the record elements are replaced at compile time.
%% So, this can't be parametrized.
change_option(Option, Value, Config) ->
@@ -817,6 +1055,7 @@ change_option(Option, Value, Config) ->
password -> Config#config{password = Value};
password_protected -> Config#config{password_protected = Value};
persistent -> Config#config{persistent = Value};
+ presence_broadcast -> Config#config{presence_broadcast = Value};
public -> Config#config{public = Value};
public_list -> Config#config{public_list = Value};
title -> Config#config{title = Value};
@@ -839,10 +1078,10 @@ get_room_options(Pid) ->
get_options(Config).
get_options(Config) ->
- Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)],
+ Fields = [misc:atom_to_binary(Field) || Field <- record_info(fields, config)],
[config | ValuesRaw] = tuple_to_list(Config),
- Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V);
- (V) when is_integer(V) -> jlib:integer_to_binary(V);
+ Values = lists:map(fun(V) when is_atom(V) -> misc:atom_to_binary(V);
+ (V) when is_integer(V) -> integer_to_binary(V);
(V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
(V) -> V end, ValuesRaw),
lists:zip(Fields, Values).
@@ -855,23 +1094,41 @@ get_options(Config) ->
%% [{JID::string(), Domain::string(), Role::string(), Reason::string()}]
%% @doc Get the affiliations of the room Name@Service.
get_room_affiliations(Name, Service) ->
- case mnesia:dirty_read(muc_online_room, {Name, Service}) of
- [R] ->
+ case mod_muc:find_online_room(Name, Service) of
+ {ok, Pid} ->
%% Get the PID of the online room, then request its state
- Pid = R#muc_online_room.pid,
- {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
- Affiliations = ?DICT:to_list(StateData#state.affiliations),
+ {ok, StateData} = mod_muc_room:get_state(Pid),
+ Affiliations = maps:to_list(StateData#state.affiliations),
lists:map(
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
{Uname, Domain, Aff, Reason};
({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
{Uname, Domain, Aff, <<>>}
end, Affiliations);
- [] ->
+ error ->
throw({error, "The room does not exist."})
end.
%%----------------------------
+%% Get Room Affiliation
+%%----------------------------
+
+%% @spec(Name::binary(), Service::binary(), JID::binary()) ->
+%% {Affiliation::string()}
+%% @doc Get affiliation of a user in the room Name@Service.
+
+get_room_affiliation(Name, Service, JID) ->
+ case mod_muc:find_online_room(Name, Service) of
+ {ok, Pid} ->
+ %% Get the PID of the online room, then request its state
+ {ok, StateData} = mod_muc_room:get_state(Pid),
+ UserJID = jid:decode(JID),
+ mod_muc_room:get_affiliation(UserJID, StateData);
+ error ->
+ throw({error, "The room does not exist."})
+ end.
+
+%%----------------------------
%% Change Room Affiliation
%%----------------------------
@@ -884,15 +1141,14 @@ get_room_affiliations(Name, Service) ->
%% If the affiliation is 'none', the action is to remove,
%% In any other case the action will be to create the affiliation.
set_room_affiliation(Name, Service, JID, AffiliationString) ->
- Affiliation = jlib:binary_to_atom(AffiliationString),
- case mnesia:dirty_read(muc_online_room, {Name, Service}) of
- [R] ->
+ Affiliation = misc:binary_to_atom(AffiliationString),
+ case mod_muc:find_online_room(Name, Service) of
+ {ok, Pid} ->
%% Get the PID for the online room so we can get the state of the room
- Pid = R#muc_online_room.pid,
- {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:from_string(JID), affiliation, Affiliation, <<"">>}, <<"">>}),
+ {ok, StateData} = mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>),
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
ok;
- [] ->
+ error ->
error
end.
@@ -904,19 +1160,15 @@ subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
throw({error, "Nickname must be set"});
subscribe_room(User, Nick, Room, Nodes) ->
NodeList = re:split(Nodes, "\\h*,\\h*"),
- case jid:from_string(Room) of
+ try jid:decode(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
- case jid:from_string(User) of
- error ->
- throw({error, "Malformed user JID"});
- #jid{lresource = <<"">>} ->
- throw({error, "User's JID should have a resource"});
- UserJID ->
+ try jid:decode(User) of
+ UserJID1 ->
+ UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
- case gen_fsm:sync_send_all_state_event(
- Pid,
- {muc_subscribe, UserJID, Nick, NodeList}) of
+ case mod_muc_room:subscribe(
+ Pid, UserJID, Nick, NodeList) of
{ok, SubscribedNodes} ->
SubscribedNodes;
{error, Reason} ->
@@ -925,23 +1177,23 @@ subscribe_room(User, Nick, Room, Nodes) ->
_ ->
throw({error, "The room does not exist"})
end
+ catch _:{bad_jid, _} ->
+ throw({error, "Malformed user JID"})
end;
_ ->
throw({error, "Malformed room JID"})
+ catch _:{bad_jid, _} ->
+ throw({error, "Malformed room JID"})
end.
unsubscribe_room(User, Room) ->
- case jid:from_string(Room) of
+ try jid:decode(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
- case jid:from_string(User) of
- error ->
- throw({error, "Malformed user JID"});
+ try jid:decode(User) of
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
- case gen_fsm:sync_send_all_state_event(
- Pid,
- {muc_unsubscribe, UserJID}) of
+ case mod_muc_room:unsubscribe(Pid, UserJID) of
ok ->
ok;
{error, Reason} ->
@@ -950,61 +1202,116 @@ unsubscribe_room(User, Room) ->
_ ->
throw({error, "The room does not exist"})
end
+ catch _:{bad_jid, _} ->
+ throw({error, "Malformed user JID"})
end;
_ ->
throw({error, "Malformed room JID"})
+ catch _:{bad_jid, _} ->
+ throw({error, "Malformed room JID"})
+ end.
+
+get_subscribers(Name, Host) ->
+ case get_room_pid(Name, Host) of
+ Pid when is_pid(Pid) ->
+ {ok, JIDList} = mod_muc_room:get_subscribers(Pid),
+ [jid:encode(jid:remove_resource(J)) || J <- JIDList];
+ _ ->
+ throw({error, "The room does not exist"})
end.
+%% Copied from mod_muc_room.erl
+get_config_opt_name(Pos) ->
+ Fs = [config|record_info(fields, config)],
+ lists:nth(Pos, Fs).
+-define(MAKE_CONFIG_OPT(Opt),
+ {get_config_opt_name(Opt), element(Opt, Config)}).
make_opts(StateData) ->
Config = StateData#state.config,
- [
- {title, Config#config.title},
- {vcard, Config#config.vcard},
- {voice_request_min_interval, Config#config.voice_request_min_interval},
- {allow_change_subj, Config#config.allow_change_subj},
- {allow_query_users, Config#config.allow_query_users},
- {allow_private_messages, Config#config.allow_private_messages},
- {allow_private_messages_from_visitors, Config#config.allow_private_messages_from_visitors},
- {allow_visitor_status, Config#config.allow_visitor_status},
- {allow_visitor_nickchange, Config#config.allow_visitor_nickchange},
- {allow_voice_requests, Config#config.allow_voice_requests},
- {public, Config#config.public},
- {public_list, Config#config.public_list},
- {persistent, Config#config.persistent},
- {mam, Config#config.mam},
- {moderated, Config#config.moderated},
- {members_by_default, Config#config.members_by_default},
- {members_only, Config#config.members_only},
- {allow_user_invites, Config#config.allow_user_invites},
- {password_protected, Config#config.password_protected},
- {password, Config#config.password},
- {anonymous, Config#config.anonymous},
- {captcha_protected, Config#config.captcha_protected},
- {description, Config#config.description},
- {logging, Config#config.logging},
- {max_users, Config#config.max_users},
- {affiliations, ?DICT:to_list(StateData#state.affiliations)},
+ Subscribers = maps:fold(
+ fun(_LJID, Sub, Acc) ->
+ [{Sub#subscriber.jid,
+ Sub#subscriber.nick,
+ Sub#subscriber.nodes}|Acc]
+ end, [], StateData#state.subscribers),
+ [?MAKE_CONFIG_OPT(#config.title), ?MAKE_CONFIG_OPT(#config.description),
+ ?MAKE_CONFIG_OPT(#config.allow_change_subj),
+ ?MAKE_CONFIG_OPT(#config.allow_query_users),
+ ?MAKE_CONFIG_OPT(#config.allow_private_messages),
+ ?MAKE_CONFIG_OPT(#config.allow_private_messages_from_visitors),
+ ?MAKE_CONFIG_OPT(#config.allow_visitor_status),
+ ?MAKE_CONFIG_OPT(#config.allow_visitor_nickchange),
+ ?MAKE_CONFIG_OPT(#config.public), ?MAKE_CONFIG_OPT(#config.public_list),
+ ?MAKE_CONFIG_OPT(#config.persistent),
+ ?MAKE_CONFIG_OPT(#config.moderated),
+ ?MAKE_CONFIG_OPT(#config.members_by_default),
+ ?MAKE_CONFIG_OPT(#config.members_only),
+ ?MAKE_CONFIG_OPT(#config.allow_user_invites),
+ ?MAKE_CONFIG_OPT(#config.password_protected),
+ ?MAKE_CONFIG_OPT(#config.captcha_protected),
+ ?MAKE_CONFIG_OPT(#config.password), ?MAKE_CONFIG_OPT(#config.anonymous),
+ ?MAKE_CONFIG_OPT(#config.logging), ?MAKE_CONFIG_OPT(#config.max_users),
+ ?MAKE_CONFIG_OPT(#config.allow_voice_requests),
+ ?MAKE_CONFIG_OPT(#config.allow_subscription),
+ ?MAKE_CONFIG_OPT(#config.mam),
+ ?MAKE_CONFIG_OPT(#config.presence_broadcast),
+ ?MAKE_CONFIG_OPT(#config.voice_request_min_interval),
+ ?MAKE_CONFIG_OPT(#config.vcard),
+ {captcha_whitelist,
+ (?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
+ {affiliations,
+ maps:to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
- {subject_author, StateData#state.subject_author}
- ].
+ {subject_author, StateData#state.subject_author},
+ {subscribers, Subscribers}].
%%----------------------------
%% Utils
%%----------------------------
-uptime_seconds() ->
- trunc(element(1, erlang:statistics(wall_clock))/1000).
-
-find_host(global) ->
- global;
-find_host("global") ->
- global;
-find_host(<<"global">>) ->
+find_service(global) ->
global;
-find_host(ServerHost) when is_list(ServerHost) ->
- find_host(list_to_binary(ServerHost));
+find_service(ServerHost) ->
+ hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
+
+find_services(Global) when Global == global;
+ Global == <<"global">> ->
+ lists:flatmap(
+ fun(ServerHost) ->
+ case gen_mod:is_loaded(ServerHost, mod_muc) of
+ true ->
+ [find_service(ServerHost)];
+ false ->
+ []
+ end
+ end, ejabberd_option:hosts());
+find_services(Service) when is_binary(Service) ->
+ [Service].
+
+get_room_serverhost(Service) when is_binary(Service) ->
+ ejabberd_router:host_of_route(Service).
+
find_host(ServerHost) ->
- gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>).
+ hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
+
+find_hosts(Global) when Global == global;
+ Global == <<"global">> ->
+ lists:flatmap(
+ fun(ServerHost) ->
+ case gen_mod:is_loaded(ServerHost, mod_muc) of
+ true ->
+ [find_host(ServerHost)];
+ false ->
+ []
+ end
+ end, ejabberd_option:hosts());
+find_hosts(ServerHost) ->
+ case gen_mod:is_loaded(ServerHost, mod_muc) of
+ true ->
+ [find_host(ServerHost)];
+ false ->
+ []
+ end.
-mod_opt_type(_) -> [].
+mod_options(_) -> [].