2015-06-22 15:56:08 +02:00
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
|
|
|
%%% @doc
|
|
|
|
%%% Message Archive Management (XEP-0313)
|
|
|
|
%%% @end
|
|
|
|
%%% Created : 4 Jul 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2016-01-13 12:29:14 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2013-2016 ProcessOne
|
2015-06-22 15:56:08 +02:00
|
|
|
%%%
|
|
|
|
%%% 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.
|
|
|
|
%%%
|
2015-08-05 09:52:54 +02:00
|
|
|
%%% 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.
|
2015-06-22 15:56:08 +02:00
|
|
|
%%%
|
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
-module(mod_mam).
|
|
|
|
|
2016-06-22 11:24:01 +02:00
|
|
|
-protocol({xep, 313, '0.5.1'}).
|
2015-12-09 22:28:44 +01:00
|
|
|
-protocol({xep, 334, '0.2'}).
|
2015-06-24 11:58:37 +02:00
|
|
|
|
2015-06-22 15:56:08 +02:00
|
|
|
-behaviour(gen_mod).
|
|
|
|
|
|
|
|
%% API
|
2016-07-06 13:58:48 +02:00
|
|
|
-export([start/2, stop/1, depends/2]).
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-04-29 13:11:08 +02:00
|
|
|
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5,
|
|
|
|
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2,
|
2016-01-26 10:00:11 +01:00
|
|
|
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
|
2016-07-09 11:43:01 +02:00
|
|
|
get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
-include("xmpp.hrl").
|
2015-06-22 15:56:08 +02:00
|
|
|
-include("logger.hrl").
|
2015-08-06 12:33:39 +02:00
|
|
|
-include("mod_muc_room.hrl").
|
2016-01-13 22:46:30 +01:00
|
|
|
-include("ejabberd_commands.hrl").
|
2016-04-15 14:11:31 +02:00
|
|
|
-include("mod_mam.hrl").
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-01-14 02:12:28 +01:00
|
|
|
-define(DEF_PAGE_SIZE, 50).
|
|
|
|
-define(MAX_PAGE_SIZE, 250).
|
|
|
|
|
2016-04-15 14:11:31 +02:00
|
|
|
-callback init(binary(), gen_mod:opts()) -> any().
|
|
|
|
-callback remove_user(binary(), binary()) -> any().
|
|
|
|
-callback remove_room(binary(), binary(), binary()) -> any().
|
|
|
|
-callback delete_old_messages(binary() | global,
|
|
|
|
erlang:timestamp(),
|
|
|
|
all | chat | groupchat) -> any().
|
2016-07-25 12:50:30 +02:00
|
|
|
-callback extended_fields() -> [xdata_field()].
|
2016-04-15 14:11:31 +02:00
|
|
|
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
|
|
|
|
jid(), binary(), recv | send) -> {ok, binary()} | any().
|
|
|
|
-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
|
|
|
|
-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
|
2016-07-25 12:50:30 +02:00
|
|
|
-callback select(binary(), jid(), jid(), mam_query(), chat | groupchat) ->
|
2016-04-15 14:11:31 +02:00
|
|
|
{[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
|
|
|
%%%===================================================================
|
|
|
|
%%% API
|
|
|
|
%%%===================================================================
|
|
|
|
start(Host, Opts) ->
|
|
|
|
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
2015-10-07 00:06:58 +02:00
|
|
|
one_queue),
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
|
|
|
Mod:init(Host, Opts),
|
|
|
|
init_cache(Opts),
|
2015-06-22 15:56:08 +02:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
2015-07-06 15:39:23 +02:00
|
|
|
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
|
2015-06-22 15:56:08 +02:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
2015-07-06 15:39:23 +02:00
|
|
|
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
|
2015-06-22 15:56:08 +02:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
2015-07-06 15:39:23 +02:00
|
|
|
?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
|
2015-06-22 15:56:08 +02:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
2015-07-06 15:39:23 +02:00
|
|
|
?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
|
2015-09-22 11:18:06 +02:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
|
|
|
?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
|
2015-06-22 15:56:08 +02:00
|
|
|
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
2016-04-29 13:11:08 +02:00
|
|
|
user_receive_packet, 88),
|
2015-06-22 15:56:08 +02:00
|
|
|
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
2016-04-29 13:11:08 +02:00
|
|
|
user_send_packet, 88),
|
|
|
|
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
|
|
|
user_send_packet_strip_tag, 500),
|
2015-08-06 12:33:39 +02:00
|
|
|
ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
|
|
|
|
muc_filter_message, 50),
|
|
|
|
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
|
|
|
|
muc_process_iq, 50),
|
2015-12-16 00:08:23 +01:00
|
|
|
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
|
|
|
disco_sm_features, 50),
|
2015-06-22 15:56:08 +02:00
|
|
|
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
|
|
|
remove_user, 50),
|
2016-07-09 11:43:01 +02:00
|
|
|
ejabberd_hooks:add(remove_room, Host, ?MODULE,
|
|
|
|
remove_room, 50),
|
|
|
|
ejabberd_hooks:add(get_room_config, Host, ?MODULE,
|
|
|
|
get_room_config, 50),
|
|
|
|
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
|
|
|
|
set_room_option, 50),
|
2015-06-22 15:56:08 +02:00
|
|
|
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
|
|
|
|
remove_user, 50),
|
2016-01-15 01:08:22 +01:00
|
|
|
case gen_mod:get_opt(assume_mam_usage, Opts,
|
|
|
|
fun(if_enabled) -> if_enabled;
|
|
|
|
(on_request) -> on_request;
|
|
|
|
(never) -> never
|
|
|
|
end, never) of
|
|
|
|
never ->
|
|
|
|
ok;
|
|
|
|
_ ->
|
|
|
|
ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
|
|
|
|
message_is_archived, 50)
|
|
|
|
end,
|
2016-01-26 10:00:11 +01:00
|
|
|
ejabberd_commands:register_commands(get_commands_spec()),
|
2015-06-22 15:56:08 +02:00
|
|
|
ok.
|
|
|
|
|
2016-04-15 14:11:31 +02:00
|
|
|
init_cache(Opts) ->
|
2015-06-22 15:56:08 +02:00
|
|
|
MaxSize = gen_mod:get_opt(cache_size, Opts,
|
2015-10-07 00:06:58 +02:00
|
|
|
fun(I) when is_integer(I), I>0 -> I end,
|
|
|
|
1000),
|
2015-06-22 15:56:08 +02:00
|
|
|
LifeTime = gen_mod:get_opt(cache_life_time, Opts,
|
2015-10-07 00:06:58 +02:00
|
|
|
fun(I) when is_integer(I), I>0 -> I end,
|
2015-06-22 15:56:08 +02:00
|
|
|
timer:hours(1) div 1000),
|
|
|
|
cache_tab:new(archive_prefs, [{max_size, MaxSize},
|
|
|
|
{life_time, LifeTime}]).
|
|
|
|
|
|
|
|
stop(Host) ->
|
|
|
|
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
2016-04-29 13:11:08 +02:00
|
|
|
user_send_packet, 88),
|
2015-06-22 15:56:08 +02:00
|
|
|
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
|
2016-04-29 13:11:08 +02:00
|
|
|
user_receive_packet, 88),
|
|
|
|
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
|
|
|
user_send_packet_strip_tag, 500),
|
2015-08-06 12:33:39 +02:00
|
|
|
ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
|
|
|
|
muc_filter_message, 50),
|
|
|
|
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
|
|
|
|
muc_process_iq, 50),
|
2015-06-22 15:56:08 +02:00
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_TMP),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_TMP),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_0),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_0),
|
2015-09-22 11:18:06 +02:00
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_1),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_1),
|
2015-12-16 00:08:23 +01:00
|
|
|
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
|
|
|
disco_sm_features, 50),
|
2015-06-22 15:56:08 +02:00
|
|
|
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
|
|
|
remove_user, 50),
|
2016-07-09 11:43:01 +02:00
|
|
|
ejabberd_hooks:delete(remove_room, Host, ?MODULE,
|
|
|
|
remove_room, 50),
|
|
|
|
ejabberd_hooks:delete(get_room_config, Host, ?MODULE,
|
|
|
|
get_room_config, 50),
|
|
|
|
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
|
|
|
|
set_room_option, 50),
|
2015-06-22 15:56:08 +02:00
|
|
|
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
|
|
|
?MODULE, remove_user, 50),
|
2016-01-15 01:08:22 +01:00
|
|
|
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage,
|
|
|
|
fun(if_enabled) -> if_enabled;
|
|
|
|
(on_request) -> on_request;
|
|
|
|
(never) -> never
|
|
|
|
end, never) of
|
|
|
|
never ->
|
|
|
|
ok;
|
|
|
|
_ ->
|
|
|
|
ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
|
|
|
|
message_is_archived, 50)
|
|
|
|
end,
|
2016-01-26 10:00:11 +01:00
|
|
|
ejabberd_commands:unregister_commands(get_commands_spec()),
|
2015-06-22 15:56:08 +02:00
|
|
|
ok.
|
|
|
|
|
2016-07-06 13:58:48 +02:00
|
|
|
depends(_Host, _Opts) ->
|
|
|
|
[].
|
|
|
|
|
2016-08-09 09:56:32 +02:00
|
|
|
-spec remove_user(binary(), binary()) -> ok.
|
2015-06-22 15:56:08 +02:00
|
|
|
remove_user(User, Server) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LUser = jid:nodeprep(User),
|
|
|
|
LServer = jid:nameprep(Server),
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-08-09 09:56:32 +02:00
|
|
|
Mod:remove_user(LUser, LServer),
|
|
|
|
ok.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-08-09 09:56:32 +02:00
|
|
|
-spec remove_room(binary(), binary(), binary()) -> ok.
|
2016-04-15 14:11:31 +02:00
|
|
|
remove_room(LServer, Name, Host) ->
|
|
|
|
LName = jid:nodeprep(Name),
|
|
|
|
LHost = jid:nameprep(Host),
|
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-08-09 09:56:32 +02:00
|
|
|
Mod:remove_room(LServer, LName, LHost),
|
|
|
|
ok.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec get_room_config([xdata_field()], mod_muc_room:state(), jid(), binary())
|
|
|
|
-> [xdata_field()].
|
|
|
|
get_room_config(XFields, RoomState, _From, Lang) ->
|
2016-07-09 11:43:01 +02:00
|
|
|
Config = RoomState#state.config,
|
|
|
|
Label = <<"Enable message archiving">>,
|
|
|
|
Var = <<"muc#roomconfig_mam">>,
|
|
|
|
Val = case Config#config.mam of
|
|
|
|
true -> <<"1">>;
|
|
|
|
_ -> <<"0">>
|
|
|
|
end,
|
2016-07-25 12:50:30 +02:00
|
|
|
XField = #xdata_field{type = boolean,
|
|
|
|
label = translate:translate(Lang, Label),
|
|
|
|
var = Var,
|
|
|
|
values = [Val]},
|
2016-08-12 12:17:42 +02:00
|
|
|
XFields ++ [XField].
|
2016-07-09 11:43:01 +02:00
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec set_room_option({pos_integer(), _}, binary(), [binary()], binary())
|
|
|
|
-> {pos_integer(), _}.
|
2016-07-10 07:45:24 +02:00
|
|
|
set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
|
2016-07-09 11:43:01 +02:00
|
|
|
try
|
|
|
|
Val = case Vals of
|
|
|
|
[<<"0">>|_] -> false;
|
|
|
|
[<<"false">>|_] -> false;
|
|
|
|
[<<"1">>|_] -> true;
|
|
|
|
[<<"true">>|_] -> true
|
|
|
|
end,
|
|
|
|
{#config.mam, Val}
|
|
|
|
catch _:{case_clause, _} ->
|
|
|
|
Txt = <<"Value of '~s' should be boolean">>,
|
|
|
|
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
|
2016-07-27 17:06:34 +02:00
|
|
|
{error, xmpp:err_bad_request(ErrTxt, Lang)}
|
2016-07-09 11:43:01 +02:00
|
|
|
end;
|
|
|
|
set_room_option(Acc, _Opt, _Vals, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec user_receive_packet(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza().
|
2015-08-12 15:58:56 +02:00
|
|
|
user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
|
2015-06-22 15:56:08 +02:00
|
|
|
LUser = JID#jid.luser,
|
|
|
|
LServer = JID#jid.lserver,
|
2015-08-12 15:58:56 +02:00
|
|
|
IsBareCopy = is_bare_copy(JID, To),
|
2015-12-29 09:54:48 +01:00
|
|
|
case should_archive(Pkt, LServer) of
|
2015-10-07 00:06:58 +02:00
|
|
|
true when not IsBareCopy ->
|
|
|
|
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
2015-08-06 12:33:39 +02:00
|
|
|
case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
|
2015-10-07 00:06:58 +02:00
|
|
|
{ok, ID} ->
|
2016-07-25 12:50:30 +02:00
|
|
|
set_stanza_id(NewPkt, LServer, ID);
|
2015-10-07 00:06:58 +02:00
|
|
|
_ ->
|
|
|
|
NewPkt
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
Pkt
|
2015-06-22 15:56:08 +02:00
|
|
|
end.
|
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
|
2015-06-22 15:56:08 +02:00
|
|
|
user_send_packet(Pkt, C2SState, JID, Peer) ->
|
|
|
|
LUser = JID#jid.luser,
|
|
|
|
LServer = JID#jid.lserver,
|
2015-12-29 09:54:48 +01:00
|
|
|
case should_archive(Pkt, LServer) of
|
2015-08-06 12:33:39 +02:00
|
|
|
true ->
|
2015-10-07 00:06:58 +02:00
|
|
|
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
2016-07-25 12:50:30 +02:00
|
|
|
case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer),
|
2016-04-29 13:11:08 +02:00
|
|
|
LUser, LServer, Peer, send) of
|
|
|
|
{ok, ID} ->
|
2016-07-25 12:50:30 +02:00
|
|
|
set_stanza_id(NewPkt, LServer, ID);
|
2016-04-29 13:11:08 +02:00
|
|
|
_ ->
|
|
|
|
NewPkt
|
|
|
|
end;
|
2015-10-07 00:06:58 +02:00
|
|
|
false ->
|
|
|
|
Pkt
|
2015-06-22 15:56:08 +02:00
|
|
|
end.
|
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec user_send_packet_strip_tag(stanza(), ejabberd_c2s:state(),
|
|
|
|
jid(), jid()) -> stanza().
|
2016-04-29 13:11:08 +02:00
|
|
|
user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) ->
|
|
|
|
LServer = JID#jid.lserver,
|
|
|
|
strip_my_archived_tag(Pkt, LServer).
|
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec muc_filter_message(message(), mod_muc_room:state(),
|
|
|
|
jid(), jid(), binary()) -> message().
|
2015-08-06 12:33:39 +02:00
|
|
|
muc_filter_message(Pkt, #state{config = Config} = MUCState,
|
|
|
|
RoomJID, From, FromNick) ->
|
|
|
|
if Config#config.mam ->
|
2015-12-15 21:37:41 +01:00
|
|
|
LServer = RoomJID#jid.lserver,
|
|
|
|
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
2016-01-19 00:39:10 +01:00
|
|
|
StorePkt = strip_x_jid_tags(NewPkt),
|
|
|
|
case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of
|
2015-09-22 20:15:34 +02:00
|
|
|
{ok, ID} ->
|
2016-07-25 12:50:30 +02:00
|
|
|
set_stanza_id(NewPkt, LServer, ID);
|
2015-09-22 20:15:34 +02:00
|
|
|
_ ->
|
|
|
|
NewPkt
|
|
|
|
end;
|
2015-08-06 12:33:39 +02:00
|
|
|
true ->
|
|
|
|
Pkt
|
|
|
|
end.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
set_stanza_id(Pkt, LServer, ID) ->
|
|
|
|
Archived = #mam_archived{by = jid:make(LServer), id = ID},
|
|
|
|
StanzaID = #stanza_id{by = jid:make(LServer), id = ID},
|
|
|
|
NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)],
|
|
|
|
xmpp:set_els(Pkt, NewEls).
|
|
|
|
|
2015-07-06 15:39:23 +02:00
|
|
|
% Query archive v0.2
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq_v0_2(#iq{from = #jid{lserver = LServer},
|
|
|
|
to = #jid{lserver = LServer},
|
|
|
|
type = get, sub_els = [#mam_query{}]} = IQ) ->
|
|
|
|
process_iq(LServer, IQ, chat);
|
|
|
|
process_iq_v0_2(IQ) ->
|
|
|
|
process_iq(IQ).
|
2015-07-06 15:39:23 +02:00
|
|
|
|
|
|
|
% Query archive v0.3
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq_v0_3(#iq{from = #jid{lserver = LServer},
|
|
|
|
to = #jid{lserver = LServer},
|
|
|
|
type = set, sub_els = [#mam_query{}]} = IQ) ->
|
|
|
|
process_iq(LServer, IQ, chat);
|
|
|
|
process_iq_v0_3(#iq{from = #jid{lserver = LServer},
|
|
|
|
to = #jid{lserver = LServer},
|
|
|
|
type = get, sub_els = [#mam_query{}]} = IQ) ->
|
2016-01-13 23:56:05 +01:00
|
|
|
process_iq(LServer, IQ);
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq_v0_3(IQ) ->
|
|
|
|
process_iq(IQ).
|
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec muc_process_iq(ignore | iq(), mod_muc_room:state()) -> ignore | iq().
|
2016-07-25 12:50:30 +02:00
|
|
|
muc_process_iq(#iq{type = T, lang = Lang,
|
|
|
|
from = From,
|
|
|
|
sub_els = [#mam_query{xmlns = NS}]} = IQ,
|
|
|
|
MUCState)
|
|
|
|
when (T == set andalso (NS == ?NS_MAM_0 orelse NS == ?NS_MAM_1)) orelse
|
|
|
|
(T == get andalso NS == ?NS_MAM_TMP) ->
|
|
|
|
case may_enter_room(From, MUCState) of
|
|
|
|
true ->
|
2016-01-13 23:56:05 +01:00
|
|
|
LServer = MUCState#state.server_host,
|
2016-07-25 12:50:30 +02:00
|
|
|
Role = mod_muc_room:get_role(From, MUCState),
|
|
|
|
process_iq(LServer, IQ, {groupchat, Role, MUCState});
|
|
|
|
false ->
|
|
|
|
Text = <<"Only members may query archives of this room">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang))
|
2016-01-13 23:56:05 +01:00
|
|
|
end;
|
2016-07-25 12:50:30 +02:00
|
|
|
muc_process_iq(#iq{type = get,
|
|
|
|
sub_els = [#mam_query{xmlns = NS}]} = IQ,
|
|
|
|
MUCState) when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
|
|
|
|
LServer = MUCState#state.server_host,
|
|
|
|
process_iq(LServer, IQ);
|
|
|
|
muc_process_iq(IQ, _MUCState) ->
|
2015-08-06 12:33:39 +02:00
|
|
|
IQ.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
parse_query(#mam_query{xdata = #xdata{fields = Fs}} = Query, Lang) ->
|
|
|
|
try
|
|
|
|
lists:foldl(
|
|
|
|
fun(#xdata_field{var = <<"start">>, values = [Data|_]}, Q) ->
|
|
|
|
case jlib:datetime_string_to_timestamp(Data) of
|
|
|
|
undefined -> throw({error, <<"start">>});
|
|
|
|
{_, _, _} = TS -> Q#mam_query{start = TS}
|
|
|
|
end;
|
|
|
|
(#xdata_field{var = <<"end">>, values = [Data|_]}, Q) ->
|
|
|
|
case jlib:datetime_string_to_timestamp(Data) of
|
|
|
|
undefined -> throw({error, <<"end">>});
|
|
|
|
{_, _, _} = TS -> Q#mam_query{'end' = TS}
|
|
|
|
end;
|
|
|
|
(#xdata_field{var = <<"with">>, values = [Data|_]}, Q) ->
|
|
|
|
case jid:from_string(Data) of
|
|
|
|
error -> throw({error, <<"with">>});
|
|
|
|
J -> Q#mam_query{with = J}
|
|
|
|
end;
|
|
|
|
(#xdata_field{var = <<"withtext">>, values = [Data|_]}, Q) ->
|
|
|
|
case Data of
|
|
|
|
<<"">> -> throw({error, <<"withtext">>});
|
|
|
|
_ -> Q#mam_query{withtext = Data}
|
|
|
|
end;
|
|
|
|
(#xdata_field{var = <<"FORM_TYPE">>, values = [NS|_]}, Q) ->
|
|
|
|
case Query#mam_query.xmlns of
|
|
|
|
NS -> Q;
|
|
|
|
_ -> throw({error, <<"FORM_TYPE">>})
|
|
|
|
end;
|
|
|
|
(#xdata_field{}, Acc) ->
|
|
|
|
Acc
|
|
|
|
end, Query, Fs)
|
|
|
|
catch throw:{error, Var} ->
|
|
|
|
Txt = io_lib:format("Incorrect value of field '~s'", [Var]),
|
|
|
|
{error, xmpp:err_bad_request(iolist_to_binary(Txt), Lang)}
|
|
|
|
end;
|
|
|
|
parse_query(Query, _Lang) ->
|
|
|
|
Query.
|
2015-07-06 15:39:23 +02:00
|
|
|
|
2015-12-16 00:08:23 +01:00
|
|
|
disco_sm_features(empty, From, To, Node, Lang) ->
|
|
|
|
disco_sm_features({result, []}, From, To, Node, Lang);
|
|
|
|
disco_sm_features({result, OtherFeatures},
|
|
|
|
#jid{luser = U, lserver = S},
|
2016-08-05 07:41:08 +02:00
|
|
|
#jid{luser = U, lserver = S}, <<"">>, _Lang) ->
|
2015-12-16 00:08:23 +01:00
|
|
|
{result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]};
|
|
|
|
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-08-12 12:17:42 +02:00
|
|
|
-spec message_is_archived(boolean(), ejabberd_c2s:state(),
|
|
|
|
jid(), jid(), message()) -> boolean().
|
2016-01-15 01:08:22 +01:00
|
|
|
message_is_archived(true, _C2SState, _Peer, _JID, _Pkt) ->
|
|
|
|
true;
|
|
|
|
message_is_archived(false, C2SState, Peer,
|
|
|
|
#jid{luser = LUser, lserver = LServer}, Pkt) ->
|
|
|
|
Res = case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage,
|
|
|
|
fun(if_enabled) -> if_enabled;
|
|
|
|
(on_request) -> on_request;
|
|
|
|
(never) -> never
|
|
|
|
end, never) of
|
|
|
|
if_enabled ->
|
2016-06-14 16:40:46 +02:00
|
|
|
case get_prefs(LUser, LServer) of
|
|
|
|
#archive_prefs{} = P ->
|
|
|
|
{ok, P};
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end;
|
2016-01-15 01:08:22 +01:00
|
|
|
on_request ->
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-01-15 01:08:22 +01:00
|
|
|
cache_tab:lookup(archive_prefs, {LUser, LServer},
|
|
|
|
fun() ->
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod:get_prefs(LUser, LServer)
|
2016-01-15 01:08:22 +01:00
|
|
|
end);
|
|
|
|
never ->
|
|
|
|
error
|
|
|
|
end,
|
|
|
|
case Res of
|
|
|
|
{ok, Prefs} ->
|
|
|
|
should_archive(strip_my_archived_tag(Pkt, LServer), LServer)
|
|
|
|
andalso should_archive_peer(C2SState, Prefs, Peer);
|
|
|
|
error ->
|
|
|
|
false
|
|
|
|
end.
|
|
|
|
|
2016-01-13 22:46:30 +01:00
|
|
|
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
|
|
|
|
TypeBin == <<"groupchat">>;
|
|
|
|
TypeBin == <<"all">> ->
|
|
|
|
Diff = Days * 24 * 60 * 60 * 1000000,
|
|
|
|
TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
|
|
|
|
Type = jlib:binary_to_atom(TypeBin),
|
2016-04-15 14:11:31 +02:00
|
|
|
DBTypes = lists:usort(
|
|
|
|
lists:map(
|
|
|
|
fun(Host) ->
|
|
|
|
case gen_mod:db_type(Host, ?MODULE) of
|
2016-04-20 11:27:32 +02:00
|
|
|
sql -> {sql, Host};
|
2016-04-15 14:11:31 +02:00
|
|
|
Other -> {Other, global}
|
|
|
|
end
|
|
|
|
end, ?MYHOSTS)),
|
|
|
|
Results = lists:map(
|
|
|
|
fun({DBType, ServerHost}) ->
|
|
|
|
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
|
|
|
Mod:delete_old_messages(ServerHost, TimeStamp, Type)
|
|
|
|
end, DBTypes),
|
2016-01-13 22:46:30 +01:00
|
|
|
case lists:filter(fun(Res) -> Res /= ok end, Results) of
|
|
|
|
[] -> ok;
|
|
|
|
[NotOk|_] -> NotOk
|
|
|
|
end;
|
|
|
|
delete_old_messages(_TypeBin, _Days) ->
|
|
|
|
unsupported_type.
|
|
|
|
|
2015-07-06 15:39:23 +02:00
|
|
|
%%%===================================================================
|
|
|
|
%%% Internal functions
|
|
|
|
%%%===================================================================
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) ->
|
|
|
|
CommonFields = [#xdata_field{type = hidden,
|
|
|
|
var = <<"FORM_TYPE">>,
|
|
|
|
values = [NS]},
|
|
|
|
#xdata_field{type = 'jid-single', var = <<"with">>},
|
|
|
|
#xdata_field{type = 'text-single', var = <<"start">>},
|
|
|
|
#xdata_field{type = 'text-single', var = <<"end">>}],
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
ExtendedFields = Mod:extended_fields(),
|
2016-07-25 12:50:30 +02:00
|
|
|
Fields = CommonFields ++ ExtendedFields,
|
|
|
|
Form = #xdata{type = form, fields = Fields},
|
|
|
|
xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = Form}).
|
2016-01-13 23:56:05 +01:00
|
|
|
|
2015-07-06 15:39:23 +02:00
|
|
|
% Preference setting (both v0.2 & v0.3)
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq(#iq{type = set, lang = Lang,
|
|
|
|
sub_els = [#mam_prefs{default = undefined, xmlns = NS}]} = IQ) ->
|
|
|
|
Why = {missing_attr, <<"default">>, <<"prefs">>, NS},
|
|
|
|
ErrTxt = xmpp:format_error(Why),
|
|
|
|
xmpp:make_error(IQ, xmpp:err_bad_request(ErrTxt, Lang));
|
|
|
|
process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
|
|
|
|
to = #jid{lserver = LServer},
|
|
|
|
type = set, lang = Lang,
|
|
|
|
sub_els = [#mam_prefs{xmlns = NS,
|
|
|
|
default = Default,
|
|
|
|
always = Always0,
|
|
|
|
never = Never0}]} = IQ) ->
|
|
|
|
Always = lists:usort(get_jids(Always0)),
|
|
|
|
Never = lists:usort(get_jids(Never0)),
|
|
|
|
case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
|
|
|
|
ok ->
|
|
|
|
NewPrefs = prefs_el(Default, Always, Never, NS),
|
|
|
|
xmpp:make_iq_result(IQ, NewPrefs);
|
|
|
|
_Err ->
|
|
|
|
Txt = <<"Database failure">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
2015-06-22 15:56:08 +02:00
|
|
|
end;
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
|
|
|
|
to = #jid{lserver = LServer},
|
|
|
|
type = get, sub_els = [#mam_prefs{xmlns = NS}]} = IQ) ->
|
2015-12-01 12:05:54 +01:00
|
|
|
Prefs = get_prefs(LUser, LServer),
|
2016-02-10 23:06:31 +01:00
|
|
|
PrefsEl = prefs_el(Prefs#archive_prefs.default,
|
|
|
|
Prefs#archive_prefs.always,
|
|
|
|
Prefs#archive_prefs.never,
|
2016-07-25 12:50:30 +02:00
|
|
|
NS),
|
|
|
|
xmpp:make_iq_result(IQ, PrefsEl);
|
|
|
|
process_iq(IQ) ->
|
|
|
|
xmpp:make_error(IQ, xmpp:err_not_allowed()).
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
|
|
|
|
sub_els = [SubEl]} = IQ, MsgType) ->
|
2016-01-13 21:38:08 +01:00
|
|
|
case MsgType of
|
|
|
|
chat ->
|
|
|
|
maybe_activate_mam(LUser, LServer);
|
|
|
|
{groupchat, _Role, _MUCState} ->
|
|
|
|
ok
|
|
|
|
end,
|
2016-07-25 12:50:30 +02:00
|
|
|
case parse_query(SubEl, Lang) of
|
|
|
|
#mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
|
|
|
|
xmpp:make_error(IQ, xmpp:err_feature_not_implemented());
|
|
|
|
#mam_query{rsm = RSM, xmlns = NS} = Query ->
|
|
|
|
NewRSM = limit_max(RSM, NS),
|
|
|
|
NewQuery = Query#mam_query{rsm = NewRSM},
|
|
|
|
select_and_send(LServer, NewQuery, IQ, MsgType);
|
|
|
|
{error, Err} ->
|
|
|
|
xmpp:make_error(IQ, Err)
|
2015-07-06 15:39:23 +02:00
|
|
|
end.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
should_archive(#message{type = T}, _LServer) when T == error; T == result ->
|
|
|
|
false;
|
|
|
|
should_archive(#message{body = Body} = Pkt, LServer) ->
|
|
|
|
case is_resent(Pkt, LServer) of
|
2016-01-14 00:58:48 +01:00
|
|
|
true ->
|
2015-10-07 00:06:58 +02:00
|
|
|
false;
|
2016-07-25 12:50:30 +02:00
|
|
|
false ->
|
|
|
|
case check_store_hint(Pkt) of
|
|
|
|
store ->
|
|
|
|
true;
|
|
|
|
no_store ->
|
2015-12-08 00:10:00 +01:00
|
|
|
false;
|
2016-07-25 12:50:30 +02:00
|
|
|
none ->
|
|
|
|
case xmpp:get_text(Body) of
|
|
|
|
<<>> ->
|
|
|
|
%% Empty body
|
2015-12-08 00:10:00 +01:00
|
|
|
false;
|
2016-07-25 12:50:30 +02:00
|
|
|
_ ->
|
|
|
|
true
|
2015-12-08 00:10:00 +01:00
|
|
|
end
|
|
|
|
end
|
2015-06-22 15:56:08 +02:00
|
|
|
end;
|
2016-07-25 12:50:30 +02:00
|
|
|
should_archive(_, _LServer) ->
|
2015-06-22 15:56:08 +02:00
|
|
|
false.
|
|
|
|
|
|
|
|
strip_my_archived_tag(Pkt, LServer) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
NewPkt = xmpp:decode_els(
|
|
|
|
Pkt, fun(El) ->
|
|
|
|
case xmpp:get_name(El) of
|
|
|
|
<<"archived">> ->
|
|
|
|
xmpp:get_ns(El) == ?NS_MAM_TMP;
|
|
|
|
<<"stanza-id">> ->
|
|
|
|
xmpp:get_ns(El) == ?NS_SID_0;
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end),
|
2015-06-22 15:56:08 +02:00
|
|
|
NewEls = lists:filter(
|
2016-07-25 12:50:30 +02:00
|
|
|
fun(#mam_archived{by = #jid{luser = <<>>} = By}) ->
|
|
|
|
By#jid.lserver /= LServer;
|
|
|
|
(#stanza_id{by = #jid{luser = <<>>} = By}) ->
|
|
|
|
By#jid.lserver /= LServer;
|
|
|
|
(_) ->
|
|
|
|
true
|
|
|
|
end, xmpp:get_els(NewPkt)),
|
|
|
|
xmpp:set_els(NewPkt, NewEls).
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-01-19 00:39:10 +01:00
|
|
|
strip_x_jid_tags(Pkt) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
NewPkt = xmpp:decode_els(
|
|
|
|
Pkt, fun(El) ->
|
|
|
|
case xmpp:get_name(El) of
|
|
|
|
<<"x">> ->
|
|
|
|
case xmpp:get_ns(El) of
|
|
|
|
?NS_MUC_USER -> true;
|
|
|
|
?NS_MUC_ADMIN -> true;
|
|
|
|
?NS_MUC_OWNER -> true;
|
|
|
|
_ -> false
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end),
|
2016-01-19 00:39:10 +01:00
|
|
|
NewEls = lists:filter(
|
2016-07-25 12:50:30 +02:00
|
|
|
fun(El) ->
|
|
|
|
Items = case El of
|
|
|
|
#muc_user{items = Is} -> Is;
|
|
|
|
#muc_admin{items = Is} -> Is;
|
|
|
|
#muc_owner{items = Is} -> Is;
|
|
|
|
_ -> []
|
|
|
|
end,
|
|
|
|
not lists:any(fun(#muc_item{jid = JID}) ->
|
|
|
|
JID /= undefined
|
|
|
|
end, Items)
|
|
|
|
end, xmpp:get_els(NewPkt)),
|
|
|
|
xmpp:set_els(NewPkt, NewEls).
|
2016-01-19 00:39:10 +01:00
|
|
|
|
2015-06-22 15:56:08 +02:00
|
|
|
should_archive_peer(C2SState,
|
2015-10-07 00:06:58 +02:00
|
|
|
#archive_prefs{default = Default,
|
|
|
|
always = Always,
|
|
|
|
never = Never},
|
|
|
|
Peer) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
LPeer = jid:remove_resource(jid:tolower(Peer)),
|
2015-06-22 15:56:08 +02:00
|
|
|
case lists:member(LPeer, Always) of
|
2015-10-07 00:06:58 +02:00
|
|
|
true ->
|
|
|
|
true;
|
|
|
|
false ->
|
|
|
|
case lists:member(LPeer, Never) of
|
|
|
|
true ->
|
|
|
|
false;
|
|
|
|
false ->
|
|
|
|
case Default of
|
|
|
|
always -> true;
|
|
|
|
never -> false;
|
|
|
|
roster ->
|
|
|
|
case ejabberd_c2s:get_subscription(
|
|
|
|
LPeer, C2SState) of
|
|
|
|
both -> true;
|
|
|
|
from -> true;
|
|
|
|
to -> true;
|
|
|
|
_ -> false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-06-22 15:56:08 +02:00
|
|
|
end.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
should_archive_muc(#message{type = groupchat,
|
|
|
|
body = Body, subject = Subj} = Pkt) ->
|
|
|
|
case check_store_hint(Pkt) of
|
|
|
|
store ->
|
|
|
|
true;
|
|
|
|
no_store ->
|
|
|
|
false;
|
|
|
|
none ->
|
|
|
|
case xmpp:get_text(Body) of
|
|
|
|
<<"">> ->
|
|
|
|
case xmpp:get_text(Subj) of
|
|
|
|
<<"">> ->
|
|
|
|
false;
|
2016-01-14 01:13:16 +01:00
|
|
|
_ ->
|
|
|
|
true
|
2016-07-25 12:50:30 +02:00
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
true
|
2016-08-09 09:56:32 +02:00
|
|
|
end
|
2016-07-25 12:50:30 +02:00
|
|
|
end;
|
|
|
|
should_archive_muc(_) ->
|
|
|
|
false.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2015-12-08 00:10:00 +01:00
|
|
|
check_store_hint(Pkt) ->
|
|
|
|
case has_store_hint(Pkt) of
|
|
|
|
true ->
|
|
|
|
store;
|
|
|
|
false ->
|
|
|
|
case has_no_store_hint(Pkt) of
|
|
|
|
true ->
|
|
|
|
no_store;
|
|
|
|
false ->
|
|
|
|
none
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
|
|
|
|
-spec has_store_hint(message()) -> boolean().
|
2015-12-08 00:10:00 +01:00
|
|
|
has_store_hint(Message) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
xmpp:has_subtag(Message, #hint{type = 'store'}).
|
2015-12-08 00:10:00 +01:00
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
-spec has_no_store_hint(message()) -> boolean().
|
2015-12-08 00:10:00 +01:00
|
|
|
has_no_store_hint(Message) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
xmpp:has_subtag(Message, #hint{type = 'no-store'}) orelse
|
|
|
|
xmpp:has_subtag(Message, #hint{type = 'no-storage'}) orelse
|
|
|
|
xmpp:has_subtag(Message, #hint{type = 'no-permanent-store'}) orelse
|
|
|
|
xmpp:has_subtag(Message, #hint{type = 'no-permanent-storage'}).
|
2015-12-08 00:10:00 +01:00
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
-spec is_resent(message(), binary()) -> boolean().
|
2015-12-29 09:54:48 +01:00
|
|
|
is_resent(Pkt, LServer) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
case xmpp:get_subtag(Pkt, #stanza_id{}) of
|
|
|
|
#stanza_id{by = #jid{luser = <<>>, lserver = LServer}} ->
|
|
|
|
true;
|
|
|
|
_ ->
|
2015-12-29 09:54:48 +01:00
|
|
|
false
|
2015-12-09 22:44:45 +01:00
|
|
|
end.
|
|
|
|
|
2016-02-08 00:16:02 +01:00
|
|
|
may_enter_room(From,
|
|
|
|
#state{config = #config{members_only = false}} = MUCState) ->
|
|
|
|
mod_muc_room:get_affiliation(From, MUCState) /= outcast;
|
|
|
|
may_enter_room(From, MUCState) ->
|
|
|
|
mod_muc_room:is_occupant_or_admin(From, MUCState).
|
|
|
|
|
2015-08-06 12:33:39 +02:00
|
|
|
store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
|
2015-06-22 15:56:08 +02:00
|
|
|
Prefs = get_prefs(LUser, LServer),
|
|
|
|
case should_archive_peer(C2SState, Prefs, Peer) of
|
2015-10-07 00:06:58 +02:00
|
|
|
true ->
|
2015-08-06 12:33:39 +02:00
|
|
|
US = {LUser, LServer},
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-07-25 12:50:30 +02:00
|
|
|
El = xmpp:encode(Pkt),
|
|
|
|
Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir);
|
2015-10-07 00:06:58 +02:00
|
|
|
false ->
|
|
|
|
pass
|
2015-06-22 15:56:08 +02:00
|
|
|
end.
|
|
|
|
|
2015-08-06 12:33:39 +02:00
|
|
|
store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
|
2016-01-14 01:13:16 +01:00
|
|
|
case should_archive_muc(Pkt) of
|
2015-08-06 12:33:39 +02:00
|
|
|
true ->
|
|
|
|
LServer = MUCState#state.server_host,
|
2015-11-24 16:44:13 +01:00
|
|
|
{U, S, _} = jid:tolower(RoomJID),
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-07-25 12:50:30 +02:00
|
|
|
El = xmpp:encode(Pkt),
|
|
|
|
Mod:store(El, LServer, {U, S}, groupchat, Peer, Nick, recv);
|
2015-08-06 12:33:39 +02:00
|
|
|
false ->
|
|
|
|
pass
|
|
|
|
end.
|
|
|
|
|
2015-06-22 15:56:08 +02:00
|
|
|
write_prefs(LUser, LServer, Host, Default, Always, Never) ->
|
|
|
|
Prefs = #archive_prefs{us = {LUser, LServer},
|
2015-10-07 00:06:58 +02:00
|
|
|
default = Default,
|
|
|
|
always = Always,
|
|
|
|
never = Never},
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(Host, ?MODULE),
|
2015-06-22 15:56:08 +02:00
|
|
|
cache_tab:dirty_insert(
|
|
|
|
archive_prefs, {LUser, LServer}, Prefs,
|
2016-04-15 14:11:31 +02:00
|
|
|
fun() -> Mod:write_prefs(LUser, LServer, Prefs, Host) end).
|
2015-06-22 15:56:08 +02:00
|
|
|
|
|
|
|
get_prefs(LUser, LServer) ->
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2015-06-22 15:56:08 +02:00
|
|
|
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
|
2016-04-15 14:11:31 +02:00
|
|
|
fun() -> Mod:get_prefs(LUser, LServer) end),
|
2015-06-22 15:56:08 +02:00
|
|
|
case Res of
|
2015-10-07 00:06:58 +02:00
|
|
|
{ok, Prefs} ->
|
|
|
|
Prefs;
|
|
|
|
error ->
|
2016-01-13 21:38:08 +01:00
|
|
|
ActivateOpt = gen_mod:get_module_opt(
|
|
|
|
LServer, ?MODULE, request_activates_archiving,
|
|
|
|
fun(B) when is_boolean(B) -> B end, false),
|
|
|
|
case ActivateOpt of
|
|
|
|
true ->
|
|
|
|
#archive_prefs{us = {LUser, LServer}, default = never};
|
|
|
|
false ->
|
|
|
|
Default = gen_mod:get_module_opt(
|
|
|
|
LServer, ?MODULE, default,
|
|
|
|
fun(always) -> always;
|
|
|
|
(never) -> never;
|
|
|
|
(roster) -> roster
|
|
|
|
end, never),
|
|
|
|
#archive_prefs{us = {LUser, LServer}, default = Default}
|
|
|
|
end
|
2015-06-22 15:56:08 +02:00
|
|
|
end.
|
|
|
|
|
2016-02-10 23:06:31 +01:00
|
|
|
prefs_el(Default, Always, Never, NS) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
#mam_prefs{default = Default,
|
|
|
|
always = [jid:make(LJ) || LJ <- Always],
|
|
|
|
never = [jid:make(LJ) || LJ <- Never],
|
|
|
|
xmlns = NS}.
|
2016-02-10 23:06:31 +01:00
|
|
|
|
2016-01-13 21:38:08 +01:00
|
|
|
maybe_activate_mam(LUser, LServer) ->
|
|
|
|
ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
|
|
|
|
request_activates_archiving,
|
|
|
|
fun(B) when is_boolean(B) -> B end,
|
|
|
|
false),
|
|
|
|
case ActivateOpt of
|
|
|
|
true ->
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-01-13 21:38:08 +01:00
|
|
|
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
|
|
|
|
fun() ->
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod:get_prefs(LUser, LServer)
|
2016-01-13 21:38:08 +01:00
|
|
|
end),
|
|
|
|
case Res of
|
|
|
|
{ok, _Prefs} ->
|
|
|
|
ok;
|
|
|
|
error ->
|
|
|
|
Default = gen_mod:get_module_opt(LServer, ?MODULE, default,
|
|
|
|
fun(always) -> always;
|
|
|
|
(never) -> never;
|
|
|
|
(roster) -> roster
|
|
|
|
end, never),
|
|
|
|
write_prefs(LUser, LServer, LServer, Default, [], [])
|
|
|
|
end;
|
|
|
|
false ->
|
|
|
|
ok
|
|
|
|
end.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
select_and_send(LServer, Query, #iq{from = From, to = To} = IQ, MsgType) ->
|
|
|
|
{Msgs, IsComplete, Count} =
|
|
|
|
case MsgType of
|
|
|
|
chat ->
|
|
|
|
select(LServer, From, From, Query, MsgType);
|
|
|
|
{groupchat, _Role, _MUCState} ->
|
|
|
|
select(LServer, From, To, Query, MsgType)
|
|
|
|
end,
|
2015-06-22 15:56:08 +02:00
|
|
|
SortedMsgs = lists:keysort(2, Msgs),
|
2016-07-25 12:50:30 +02:00
|
|
|
send(SortedMsgs, Count, IsComplete, IQ).
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
select(_LServer, JidRequestor, JidArchive,
|
|
|
|
#mam_query{start = Start, 'end' = End, rsm = RSM},
|
2015-09-01 10:37:07 +02:00
|
|
|
{groupchat, _Role, #state{config = #config{mam = false},
|
2016-04-15 14:11:31 +02:00
|
|
|
history = History}} = MsgType) ->
|
2015-09-01 10:37:07 +02:00
|
|
|
#lqueue{len = L, queue = Q} = History,
|
|
|
|
{Msgs0, _} =
|
|
|
|
lists:mapfoldl(
|
|
|
|
fun({Nick, Pkt, _HaveSubject, UTCDateTime, _Size}, I) ->
|
|
|
|
Now = datetime_to_now(UTCDateTime, I),
|
|
|
|
TS = now_to_usec(Now),
|
|
|
|
case match_interval(Now, Start, End) and
|
|
|
|
match_rsm(Now, RSM) of
|
|
|
|
true ->
|
2016-07-25 12:50:30 +02:00
|
|
|
{[{integer_to_binary(TS), TS,
|
2015-09-01 10:37:07 +02:00
|
|
|
msg_to_el(#archive_msg{
|
|
|
|
type = groupchat,
|
|
|
|
timestamp = Now,
|
|
|
|
peer = undefined,
|
|
|
|
nick = Nick,
|
|
|
|
packet = Pkt},
|
2016-01-20 00:02:40 +01:00
|
|
|
MsgType, JidRequestor, JidArchive)}],
|
|
|
|
I+1};
|
2015-09-01 10:37:07 +02:00
|
|
|
false ->
|
|
|
|
{[], I+1}
|
|
|
|
end
|
|
|
|
end, 0, queue:to_list(Q)),
|
|
|
|
Msgs = lists:flatten(Msgs0),
|
|
|
|
case RSM of
|
2016-07-25 12:50:30 +02:00
|
|
|
#rsm_set{max = Max, before = Before} when is_binary(Before) ->
|
2015-09-01 10:37:07 +02:00
|
|
|
{NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max),
|
|
|
|
{NewMsgs, IsComplete, L};
|
2016-07-25 12:50:30 +02:00
|
|
|
#rsm_set{max = Max} ->
|
2015-09-01 10:37:07 +02:00
|
|
|
{NewMsgs, IsComplete} = filter_by_max(Msgs, Max),
|
|
|
|
{NewMsgs, IsComplete, L};
|
|
|
|
_ ->
|
|
|
|
{Msgs, true, L}
|
|
|
|
end;
|
2016-07-25 12:50:30 +02:00
|
|
|
select(LServer, JidRequestor, JidArchive, Query, MsgType) ->
|
2016-04-15 14:11:31 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-07-25 12:50:30 +02:00
|
|
|
Mod:select(LServer, JidRequestor, JidArchive, Query, MsgType).
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2015-08-06 12:33:39 +02:00
|
|
|
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
|
2016-01-20 00:02:40 +01:00
|
|
|
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
|
|
|
|
Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType,
|
|
|
|
Nick),
|
2016-07-25 12:50:30 +02:00
|
|
|
#forwarded{sub_els = [Pkt2],
|
|
|
|
delay = #delay{stamp = TS, from = jid:make(LServer)}}.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-09-13 11:30:05 +02:00
|
|
|
maybe_update_from_to(#xmlel{} = El, JidRequestor, JidArchive, Peer,
|
|
|
|
{groupchat, _, _} = MsgType, Nick) ->
|
|
|
|
Pkt = xmpp:decode(El, [ignore_els]),
|
|
|
|
maybe_update_from_to(Pkt, JidRequestor, JidArchive, Peer, MsgType, Nick);
|
2016-07-25 12:50:30 +02:00
|
|
|
maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive,
|
2016-01-20 00:02:40 +01:00
|
|
|
Peer, {groupchat, Role,
|
|
|
|
#state{config = #config{anonymous = Anon}}},
|
|
|
|
Nick) ->
|
|
|
|
ExposeJID = case {Peer, JidRequestor} of
|
|
|
|
{undefined, _JidRequestor} ->
|
|
|
|
false;
|
|
|
|
{{U, S, _R}, #jid{luser = U, lserver = S}} ->
|
|
|
|
true;
|
|
|
|
{_Peer, _JidRequestor} when not Anon; Role == moderator ->
|
|
|
|
true;
|
|
|
|
{_Peer, _JidRequestor} ->
|
|
|
|
false
|
|
|
|
end,
|
|
|
|
Items = case ExposeJID of
|
|
|
|
true ->
|
2016-07-25 12:50:30 +02:00
|
|
|
[#muc_user{items = [#muc_item{jid = Peer}]}];
|
2016-01-20 00:02:40 +01:00
|
|
|
false ->
|
2015-08-06 12:33:39 +02:00
|
|
|
[]
|
|
|
|
end,
|
2016-07-25 12:50:30 +02:00
|
|
|
Pkt#message{from = jid:replace_resource(JidArchive, Nick),
|
|
|
|
to = undefined,
|
|
|
|
sub_els = Items ++ Els};
|
2016-01-20 00:02:40 +01:00
|
|
|
maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
|
2016-01-18 13:27:29 +01:00
|
|
|
Pkt.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2015-08-12 15:58:56 +02:00
|
|
|
is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
|
|
|
|
PrioRes = ejabberd_sm:get_user_present_resources(U, S),
|
|
|
|
MaxRes = case catch lists:max(PrioRes) of
|
|
|
|
{_Prio, Res} when is_binary(Res) ->
|
|
|
|
Res;
|
|
|
|
_ ->
|
|
|
|
undefined
|
|
|
|
end,
|
|
|
|
IsBareTo = case To of
|
|
|
|
#jid{lresource = <<"">>} ->
|
|
|
|
true;
|
|
|
|
#jid{lresource = LRes} ->
|
|
|
|
%% Unavailable resources are handled like bare JIDs.
|
|
|
|
lists:keyfind(LRes, 2, PrioRes) =:= false
|
|
|
|
end,
|
|
|
|
case {IsBareTo, R} of
|
|
|
|
{true, MaxRes} ->
|
|
|
|
?DEBUG("Recipient of message to bare JID has top priority: ~s@~s/~s",
|
|
|
|
[U, S, R]),
|
|
|
|
false;
|
|
|
|
{true, _R} ->
|
|
|
|
%% The message was sent to our bare JID, and we currently have
|
|
|
|
%% multiple resources with the same highest priority, so the session
|
|
|
|
%% manager routes the message to each of them. We store the message
|
|
|
|
%% only from the resource where R equals MaxRes.
|
|
|
|
?DEBUG("Additional recipient of message to bare JID: ~s@~s/~s",
|
|
|
|
[U, S, R]),
|
|
|
|
true;
|
|
|
|
{false, _R} ->
|
|
|
|
false
|
|
|
|
end.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
-spec send([{binary(), integer(), xmlel()}],
|
|
|
|
non_neg_integer(), boolean(), iq()) -> iq() | ignore.
|
|
|
|
send(Msgs, Count, IsComplete,
|
|
|
|
#iq{from = From, to = To,
|
|
|
|
sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) ->
|
2015-06-22 15:56:08 +02:00
|
|
|
Els = lists:map(
|
|
|
|
fun({ID, _IDInt, El}) ->
|
2016-07-25 12:50:30 +02:00
|
|
|
#message{sub_els = [#mam_result{xmlns = NS,
|
|
|
|
id = ID,
|
|
|
|
queryid = QID,
|
|
|
|
sub_els = [El]}]}
|
2015-06-22 15:56:08 +02:00
|
|
|
end, Msgs),
|
2016-07-25 12:50:30 +02:00
|
|
|
RSMOut = make_rsm_out(Msgs, Count),
|
|
|
|
Result = if NS == ?NS_MAM_TMP ->
|
|
|
|
#mam_query{xmlns = NS, id = QID, rsm = RSMOut};
|
|
|
|
true ->
|
2016-09-13 11:30:05 +02:00
|
|
|
#mam_fin{xmlns = NS, id = QID, rsm = RSMOut,
|
|
|
|
complete = IsComplete}
|
2016-07-25 12:50:30 +02:00
|
|
|
end,
|
2015-09-22 11:18:06 +02:00
|
|
|
if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 ->
|
2015-06-22 15:56:08 +02:00
|
|
|
lists:foreach(
|
|
|
|
fun(El) ->
|
|
|
|
ejabberd_router:route(To, From, El)
|
|
|
|
end, Els),
|
2016-07-25 12:50:30 +02:00
|
|
|
xmpp:make_iq_result(IQ, Result);
|
2015-09-22 11:18:06 +02:00
|
|
|
NS == ?NS_MAM_0 ->
|
2016-07-25 12:50:30 +02:00
|
|
|
ejabberd_router:route(To, From, xmpp:make_iq_result(IQ)),
|
2015-06-22 15:56:08 +02:00
|
|
|
lists:foreach(
|
|
|
|
fun(El) ->
|
|
|
|
ejabberd_router:route(To, From, El)
|
|
|
|
end, Els),
|
2016-07-25 12:50:30 +02:00
|
|
|
ejabberd_router:route(To, From, #message{sub_els = [Result]}),
|
2015-06-22 15:56:08 +02:00
|
|
|
ignore
|
|
|
|
end.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
-spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set().
|
|
|
|
make_rsm_out([], Count) ->
|
|
|
|
#rsm_set{count = Count};
|
|
|
|
make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) ->
|
2015-06-22 15:56:08 +02:00
|
|
|
{LastID, _, _} = lists:last(Msgs),
|
2016-07-25 12:50:30 +02:00
|
|
|
#rsm_set{first = #rsm_first{data = FirstID}, last = LastID, count = Count}.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
|
|
|
filter_by_max(Msgs, undefined) ->
|
2015-07-10 12:02:42 +02:00
|
|
|
{Msgs, true};
|
2015-06-22 15:56:08 +02:00
|
|
|
filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
|
2015-07-10 12:02:42 +02:00
|
|
|
{lists:sublist(Msgs, Len), length(Msgs) =< Len};
|
2015-06-22 15:56:08 +02:00
|
|
|
filter_by_max(_Msgs, _Junk) ->
|
2015-07-10 12:02:42 +02:00
|
|
|
{[], true}.
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
-spec limit_max(rsm_set(), binary()) -> rsm_set().
|
2016-01-14 02:12:28 +01:00
|
|
|
limit_max(RSM, ?NS_MAM_TMP) ->
|
|
|
|
RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
|
2016-07-25 12:50:30 +02:00
|
|
|
limit_max(#rsm_set{max = Max} = RSM, _NS) when not is_integer(Max) ->
|
|
|
|
RSM#rsm_set{max = ?DEF_PAGE_SIZE};
|
|
|
|
limit_max(#rsm_set{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
|
|
|
|
RSM#rsm_set{max = ?MAX_PAGE_SIZE};
|
2016-01-14 02:12:28 +01:00
|
|
|
limit_max(RSM, _NS) ->
|
|
|
|
RSM.
|
|
|
|
|
2016-09-13 11:30:05 +02:00
|
|
|
match_interval(Now, Start, undefined) ->
|
|
|
|
Now >= Start;
|
2015-09-01 10:37:07 +02:00
|
|
|
match_interval(Now, Start, End) ->
|
|
|
|
(Now >= Start) and (Now =< End).
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
|
|
|
|
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
2015-09-01 10:37:07 +02:00
|
|
|
Now > Now1;
|
2016-07-25 12:50:30 +02:00
|
|
|
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
|
|
|
|
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
2015-09-01 10:37:07 +02:00
|
|
|
Now < Now1;
|
|
|
|
match_rsm(_Now, _) ->
|
|
|
|
true.
|
|
|
|
|
2015-06-22 15:56:08 +02:00
|
|
|
now_to_usec({MSec, Sec, USec}) ->
|
|
|
|
(MSec*1000000 + Sec)*1000000 + USec.
|
|
|
|
|
|
|
|
usec_to_now(Int) ->
|
|
|
|
Secs = Int div 1000000,
|
|
|
|
USec = Int rem 1000000,
|
|
|
|
MSec = Secs div 1000000,
|
|
|
|
Sec = Secs rem 1000000,
|
|
|
|
{MSec, Sec, USec}.
|
|
|
|
|
2015-09-01 10:37:07 +02:00
|
|
|
datetime_to_now(DateTime, USecs) ->
|
|
|
|
Seconds = calendar:datetime_to_gregorian_seconds(DateTime) -
|
|
|
|
calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
|
|
|
|
{Seconds div 1000000, Seconds rem 1000000, USecs}.
|
|
|
|
|
2016-07-25 12:50:30 +02:00
|
|
|
get_jids(undefined) ->
|
|
|
|
[];
|
|
|
|
get_jids(Js) ->
|
|
|
|
[jid:tolower(jid:remove_resource(J)) || J <- Js].
|
2015-06-22 15:56:08 +02:00
|
|
|
|
2016-01-26 10:00:11 +01:00
|
|
|
get_commands_spec() ->
|
2016-01-13 22:46:30 +01:00
|
|
|
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
|
|
|
|
desc = "Delete MAM messages older than DAYS",
|
|
|
|
longdesc = "Valid message TYPEs: "
|
|
|
|
"\"chat\", \"groupchat\", \"all\".",
|
|
|
|
module = ?MODULE, function = delete_old_messages,
|
|
|
|
args = [{type, binary}, {days, integer}],
|
|
|
|
result = {res, rescode}}].
|
|
|
|
|
2016-01-15 01:08:22 +01:00
|
|
|
mod_opt_type(assume_mam_usage) ->
|
|
|
|
fun(if_enabled) -> if_enabled;
|
|
|
|
(on_request) -> on_request;
|
|
|
|
(never) -> never
|
|
|
|
end;
|
2015-06-22 15:56:08 +02:00
|
|
|
mod_opt_type(cache_life_time) ->
|
|
|
|
fun (I) when is_integer(I), I > 0 -> I end;
|
|
|
|
mod_opt_type(cache_size) ->
|
|
|
|
fun (I) when is_integer(I), I > 0 -> I end;
|
2016-04-27 16:10:50 +02:00
|
|
|
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
2015-06-22 15:56:08 +02:00
|
|
|
mod_opt_type(default) ->
|
|
|
|
fun (always) -> always;
|
|
|
|
(never) -> never;
|
|
|
|
(roster) -> roster
|
|
|
|
end;
|
|
|
|
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
2016-01-13 21:38:08 +01:00
|
|
|
mod_opt_type(request_activates_archiving) ->
|
|
|
|
fun (B) when is_boolean(B) -> B end;
|
2015-06-22 15:56:08 +02:00
|
|
|
mod_opt_type(_) ->
|
2016-01-15 01:08:22 +01:00
|
|
|
[assume_mam_usage, cache_life_time, cache_size, db_type, default, iqdisc,
|
2016-01-18 13:27:29 +01:00
|
|
|
request_activates_archiving].
|