-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
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, _} ->
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) ->
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.
{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)},
-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,
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").
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,