diff --git a/src/mod_mam.erl b/src/mod_mam.erl index e066f6d0e..8d672a7b1 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -76,8 +76,12 @@ -callback remove_from_archive(binary(), binary(), jid() | none) -> ok | {error, any()}. -callback is_empty_for_user(binary(), binary()) -> boolean(). -callback is_empty_for_room(binary(), binary(), binary()) -> boolean(). +-callback select_with_mucsub(binary(), jid(), jid(), mam_query:result(), + #rsm_set{} | undefined) -> + {[{binary(), non_neg_integer(), xmlel()}], boolean(), count()} | + {error, db_failure}. --optional_callbacks([use_cache/1, cache_nodes/1]). +-optional_callbacks([use_cache/1, cache_nodes/1, select_with_mucsub/5]). %%%=================================================================== %%% API @@ -886,16 +890,20 @@ may_enter_room(From, MUCState) -> store_msg(Pkt, LUser, LServer, Peer, Dir) -> case get_prefs(LUser, LServer) of {ok, Prefs} -> - case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt} of - {true, #message{meta = #{sm_copy := true}}} -> + UseMucArchive = gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive), + StoredInMucMam = UseMucArchive andalso xmpp:get_meta(Pkt, in_muc_mam, false), + case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt, StoredInMucMam} of + {true, #message{meta = #{sm_copy := true}}, _} -> ok; % Already stored. - {true, _} -> + {true, _, true} -> + ok; % Stored in muc archive. + {true, _, _} -> case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt, [LUser, LServer, Peer, <<"">>, chat, Dir]) of #message{} -> ok; _ -> pass end; - {false, _} -> + {false, _, _} -> pass end; {error, _} -> @@ -1073,10 +1081,118 @@ select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) -> true -> {[], true, 0}; false -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) + case {MsgType, gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive)} of + {chat, true} -> + select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM); + _ -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) + end end. +select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM) -> + MucHosts = mod_muc_admin:find_hosts(LServer), + Mod = gen_mod:db_mod(LServer, ?MODULE), + case proplists:get_value(with, Query) of + #jid{lserver = WithLServer} = MucJid -> + case lists:member(WithLServer, MucHosts) of + true -> + select(LServer, JidRequestor, MucJid, Query, RSM, + {groupchat, member, #state{config = #config{mam = true}}}); + _ -> + Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, chat) + end; + _ -> + case erlang:function_exported(Mod, select_with_mucsub, 5) of + true -> + Mod:select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM); + false -> + case Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, chat) of + {error, _} = Err -> + Err; + {Entries, All, Count} -> + {Dir, Max} = case RSM of + #rsm_set{max = M, before = V} when is_binary(V) -> + {desc, M}; + #rsm_set{max = M} -> + {asc, M}; + _ -> + {asc, undefined} + end, + SubRooms = case mod_muc_admin:find_hosts(LServer) of + [First|_] -> + mod_muc:get_subscribed_rooms(First, JidRequestor); + _ -> + [] + end, + SubRoomJids = [Jid || #muc_subscription{jid = Jid} <- SubRooms], + {E2, A2, C2} = lists:foldl( + fun(MucJid, {E0, A0, C0}) -> + case select(LServer, JidRequestor, MucJid, Query, RSM, + {groupchat, member, #state{config = #config{mam = true}}}) of + {error, _} -> + {E0, A0, C0}; + {E, A, C} -> + {lists:keymerge(2, E0, wrap_as_mucsub(E, JidRequestor)), + A0 andalso A, C0 + C} + end + end, {Entries, All, Count}, SubRoomJids), + case {Dir, Max} of + {_, undefined} -> + {E2, A2, C2}; + {desc, _} -> + Start = case length(E2) of + Len when Len < Max -> 1; + Len -> Len - Max + 1 + end, + Sub = lists:sublist(E2, Start, Max), + {Sub, if Sub == E2 -> A2; true -> false end, C2}; + _ -> + Sub = lists:sublist(E2, 1, Max), + {Sub, if Sub == E2 -> A2; true -> false end, C2} + end + end + end + end. + +wrap_as_mucsub(Messages, #jid{lserver = LServer} = Requester) -> + ReqBare = jid:remove_resource(Requester), + ReqServer = jid:make(<<>>, LServer, <<>>), + [{T1, T2, wrap_as_mucsub(M, ReqBare, ReqServer)} || {T1, T2, M} <- Messages]. + +wrap_as_mucsub(Message, Requester, ReqServer) -> + case Message of + #forwarded{delay = #delay{stamp = Stamp, desc = Desc}, + sub_els = [#message{from = From, sub_els = SubEls} = Msg]} -> + {L1, SubEls2} = case lists:keytake(mam_archived, 1, xmpp:decode(SubEls)) of + {value, Arch, Rest} -> + {[Arch#mam_archived{by = Requester}], Rest}; + _ -> + {[], SubEls} + end, + {Sid, L2, SubEls3} = case lists:keytake(stanza_id, 1, SubEls2) of + {value, #stanza_id{id = Sid0} = SID, Rest2} -> + {Sid0, [SID#stanza_id{by = Requester} | L1], Rest2}; + _ -> + {p1_rand:get_string(), L1, SubEls2} + end, + Msg2 = Msg#message{to = Requester, sub_els = SubEls3}, + #forwarded{delay = #delay{stamp = Stamp, desc = Desc, from = ReqServer}, + sub_els = [ + #message{from = jid:remove_resource(From), to = Requester, + id = Sid, + sub_els = [#ps_event{ + items = #ps_items{ + node = ?NS_MUCSUB_NODES_MESSAGES, + items = [#ps_item{ + id = Sid, + sub_els = [Msg2] + }]}} | L2]}]}; + _ -> + Message + end. + + msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick, peer = Peer, id = ID}, MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) -> @@ -1265,6 +1381,8 @@ mod_opt_type(request_activates_archiving) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(clear_archive_on_room_destroy) -> fun (B) when is_boolean(B) -> B end; +mod_opt_type(user_mucsub_from_muc_archive) -> + fun (B) when is_boolean(B) -> B end; mod_opt_type(access_preferences) -> fun acl:access_rules_validator/1. @@ -1275,6 +1393,7 @@ mod_options(Host) -> {compress_xml, false}, {clear_archive_on_room_destroy, true}, {access_preferences, all}, + {user_mucsub_from_muc_archive, false}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, {use_cache, ejabberd_config:use_cache(Host)}, {cache_size, ejabberd_config:cache_size(Host)}, diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 366af53d6..c2701fa2b 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -65,7 +65,8 @@ iq_set_register_info/5, count_online_rooms_by_user/3, get_online_rooms_by_user/3, - can_use_nick/4]). + can_use_nick/4, + get_subscribed_rooms/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, @@ -727,6 +728,11 @@ get_room_disco_item({Name, Host, Pid}, Query) -> {error, notfound} end. +-spec get_subscribed_rooms(binary(), jid()) -> [#muc_subscription{}]. +get_subscribed_rooms(Host, User) -> + ServerHost = ejabberd_router:host_of_route(Host), + get_subscribed_rooms(ServerHost, Host, User). + get_subscribed_rooms(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index d7122afbe..3672c2b9c 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -28,7 +28,7 @@ -behaviour(gen_mod). --export([start/2, stop/1, reload/3, depends/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, @@ -41,7 +41,7 @@ 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, get_subscribers/2, - web_page_host/3, mod_options/1, get_commands_spec/0]). + web_page_host/3, mod_options/1, get_commands_spec/0, find_hosts/1]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -100,18 +100,18 @@ get_commands_spec() -> desc = "List existing rooms ('global' to get all vhosts) by regex", policy = admin, module = ?MODULE, function = muc_online_rooms_by_regex, - args_desc = ["Server domain where the MUC service is, or 'global' for all", + args_desc = ["Server domain where the MUC service is, or 'global' for all", "Regex pattern for room name"], args_example = ["example.com", "^prefix"], result_desc = "List of rooms with summary", - result_example = [{"room1@muc.example.com", "true", 10}, + result_example = [{"room1@muc.example.com", "true", 10}, {"room2@muc.example.com", "false", 10}], args = [{host, binary}, {regex, binary}], 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 the MUC service of a server", module = ?MODULE, function = muc_register_nick, diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 1627b8866..cbc967da4 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -4397,9 +4397,17 @@ send_wrapped(From, To, Packet, Node, State) -> #subscriber{nodes = Nodes, jid = JID} -> case lists:member(Node, Nodes) of true -> - NewPacket = wrap(From, JID, Packet, Node), + MamEnabled = (State#state.config)#config.mam, + Id = case xmpp:get_subtag(Packet, #stanza_id{}) of + #stanza_id{id = Id2} -> + Id2; + _ -> + p1_rand:get_string() + end, + NewPacket = wrap(From, JID, Packet, Node, Id), + NewPacket2 = xmpp:put_meta(NewPacket, in_muc_mam, MamEnabled), ejabberd_router:route( - xmpp:set_from_to(NewPacket, State#state.jid, JID)); + xmpp:set_from_to(NewPacket2, State#state.jid, JID)); false -> ok end @@ -4432,10 +4440,9 @@ send_wrapped(From, To, Packet, Node, State) -> ejabberd_router:route(xmpp:set_from_to(Packet, From, To)) end. --spec wrap(jid(), jid(), stanza(), binary()) -> message(). -wrap(From, To, Packet, Node) -> +-spec wrap(jid(), jid(), stanza(), binary(), binary()) -> message(). +wrap(From, To, Packet, Node, Id) -> El = xmpp:set_from_to(Packet, From, To), - Id = p1_rand:get_string(), #message{ id = Id, sub_els = [#ps_event{