diff options
Diffstat (limited to 'src/mod_muc_admin.erl')
-rw-r--r-- | src/mod_muc_admin.erl | 927 |
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(_) -> []. |