Add support for MUC MAM
This commit is contained in:
parent
c71d57a05d
commit
40feed723d
|
@ -61,7 +61,8 @@
|
|||
max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none,
|
||||
logging = false :: boolean(),
|
||||
vcard = <<"">> :: binary(),
|
||||
captcha_whitelist = (?SETS):empty() :: ?TGB_SET
|
||||
captcha_whitelist = (?SETS):empty() :: ?TGB_SET,
|
||||
mam = false :: boolean()
|
||||
}).
|
||||
|
||||
-type config() :: #config{}.
|
||||
|
|
|
@ -279,6 +279,8 @@ CREATE TABLE archive (
|
|||
xml text NOT NULL,
|
||||
txt text,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kind text,
|
||||
nick text,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
|
|
@ -93,6 +93,8 @@ CREATE TABLE archive (
|
|||
xml text NOT NULL,
|
||||
txt text,
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
kind varchar(10),
|
||||
nick varchar(250),
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8;
|
||||
|
||||
|
|
|
@ -93,6 +93,8 @@ CREATE TABLE archive (
|
|||
xml text NOT NULL,
|
||||
txt text,
|
||||
id SERIAL,
|
||||
kind text,
|
||||
nick text,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
|
|
242
src/mod_mam.erl
242
src/mod_mam.erl
|
@ -34,11 +34,12 @@
|
|||
|
||||
-export([user_send_packet/4, user_receive_packet/5,
|
||||
process_iq_v0_2/3, process_iq_v0_3/3, remove_user/2,
|
||||
mod_opt_type/1]).
|
||||
mod_opt_type/1, muc_process_iq/4, muc_filter_message/5]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_muc_room.hrl").
|
||||
|
||||
-record(archive_msg,
|
||||
{us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
|
||||
|
@ -46,7 +47,9 @@
|
|||
timestamp = now() :: erlang:timestamp() | '_' | '$1',
|
||||
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
|
||||
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
|
||||
packet = #xmlel{} :: xmlel() | '_'}).
|
||||
packet = #xmlel{} :: xmlel() | '_',
|
||||
nick = <<"">> :: binary(),
|
||||
type = chat :: chat | groupchat}).
|
||||
|
||||
-record(archive_prefs,
|
||||
{us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
|
@ -75,19 +78,16 @@ start(Host, Opts) ->
|
|||
user_receive_packet, 500),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 500),
|
||||
ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
|
||||
muc_process_iq, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ok.
|
||||
|
||||
init_db(odbc, Host) ->
|
||||
Muchost = gen_mod:get_module_opt_host(Host, mod_muc,
|
||||
<<"conference.@HOST@">>),
|
||||
ets:insert(ejabberd_modules, {ejabberd_module, {mod_mam, Muchost},
|
||||
[{db_type, odbc}]}),
|
||||
mnesia:dirty_write({local_config, {modules,Muchost},
|
||||
[{mod_mam, [{db_type, odbc}]}]});
|
||||
init_db(mnesia, _Host) ->
|
||||
mnesia:create_table(archive_msg,
|
||||
[{disc_only_copies, [node()]},
|
||||
|
@ -114,6 +114,10 @@ stop(Host) ->
|
|||
user_send_packet, 500),
|
||||
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
|
||||
user_receive_packet, 500),
|
||||
ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
|
||||
muc_process_iq, 50),
|
||||
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),
|
||||
|
@ -152,8 +156,7 @@ user_receive_packet(Pkt, C2SState, JID, Peer, _To) ->
|
|||
case should_archive(Pkt) of
|
||||
true ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
||||
case store(C2SState, NewPkt, LUser, LServer,
|
||||
Peer, true, recv) of
|
||||
case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
|
||||
{ok, ID} ->
|
||||
Archived = #xmlel{name = <<"archived">>,
|
||||
attrs = [{<<"by">>, LServer},
|
||||
|
@ -164,8 +167,6 @@ user_receive_packet(Pkt, C2SState, JID, Peer, _To) ->
|
|||
_ ->
|
||||
NewPkt
|
||||
end;
|
||||
muc ->
|
||||
Pkt;
|
||||
false ->
|
||||
Pkt
|
||||
end.
|
||||
|
@ -174,29 +175,25 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
|
|||
LUser = JID#jid.luser,
|
||||
LServer = JID#jid.lserver,
|
||||
case should_archive(Pkt) of
|
||||
S when (S==true) ->
|
||||
true ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
||||
store0(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
LUser, LServer, Peer, S, send),
|
||||
store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
LUser, LServer, Peer, send),
|
||||
NewPkt;
|
||||
S when (S==muc) ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, LServer),
|
||||
case store0(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
|
||||
LUser, LServer, Peer, S, send) of
|
||||
{ok, ID} ->
|
||||
By = jlib:jid_to_string(Peer),
|
||||
Archived = #xmlel{name = <<"archived">>,
|
||||
attrs = [{<<"by">>, By}, {<<"xmlns">>, ?NS_MAM_TMP},
|
||||
{<<"id">>, ID}]},
|
||||
NewEls = [Archived|NewPkt#xmlel.children],
|
||||
NewPkt#xmlel{children = NewEls};
|
||||
_ ->
|
||||
NewPkt
|
||||
end;
|
||||
false ->
|
||||
Pkt
|
||||
end.
|
||||
|
||||
muc_filter_message(Pkt, #state{config = Config} = MUCState,
|
||||
RoomJID, From, FromNick) ->
|
||||
if Config#config.mam ->
|
||||
NewPkt = strip_my_archived_tag(Pkt, MUCState#state.server_host),
|
||||
store_muc(MUCState, NewPkt, RoomJID, From, FromNick),
|
||||
NewPkt;
|
||||
true ->
|
||||
Pkt
|
||||
end.
|
||||
|
||||
% Query archive v0.2
|
||||
process_iq_v0_2(#jid{lserver = LServer} = From,
|
||||
#jid{lserver = LServer} = To,
|
||||
|
@ -217,7 +214,7 @@ process_iq_v0_2(#jid{lserver = LServer} = From,
|
|||
(_) ->
|
||||
[]
|
||||
end, SubEl#xmlel.children),
|
||||
process_iq(From, To, IQ, SubEl, Fs);
|
||||
process_iq(LServer, From, To, IQ, SubEl, Fs, chat);
|
||||
process_iq_v0_2(From, To, IQ) ->
|
||||
process_iq(From, To, IQ).
|
||||
|
||||
|
@ -225,7 +222,28 @@ process_iq_v0_2(From, To, IQ) ->
|
|||
process_iq_v0_3(#jid{lserver = LServer} = From,
|
||||
#jid{lserver = LServer} = To,
|
||||
#iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
|
||||
Fs = case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
|
||||
process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat);
|
||||
process_iq_v0_3(From, To, IQ) ->
|
||||
process_iq(From, To, IQ).
|
||||
|
||||
muc_process_iq(#iq{type = set,
|
||||
sub_el = #xmlel{name = <<"query">>,
|
||||
attrs = Attrs} = SubEl} = IQ,
|
||||
MUCState, From, To) ->
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_MAM_0 ->
|
||||
LServer = MUCState#state.server_host,
|
||||
Role = mod_muc_room:get_role(From, MUCState),
|
||||
process_iq(LServer, From, To, IQ, SubEl,
|
||||
get_xdata_fields(SubEl), {groupchat, Role});
|
||||
_ ->
|
||||
IQ
|
||||
end;
|
||||
muc_process_iq(IQ, _MUCState, _From, _To) ->
|
||||
IQ.
|
||||
|
||||
get_xdata_fields(SubEl) ->
|
||||
case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
|
||||
xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
|
||||
{#xmlel{} = XData, false} ->
|
||||
jlib:parse_xdata_submit(XData);
|
||||
|
@ -235,10 +253,7 @@ process_iq_v0_3(#jid{lserver = LServer} = From,
|
|||
[{<<"set">>, SubEl}];
|
||||
{false, false} ->
|
||||
[]
|
||||
end,
|
||||
process_iq(From, To, IQ, SubEl, Fs);
|
||||
process_iq_v0_3(From, To, IQ) ->
|
||||
process_iq(From, To, IQ).
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
|
@ -276,7 +291,7 @@ process_iq(#jid{luser = LUser, lserver = LServer},
|
|||
process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
|
||||
process_iq(From, To, IQ, SubEl, Fs) ->
|
||||
process_iq(LServer, From, To, IQ, SubEl, Fs, MsgType) ->
|
||||
case catch lists:foldl(
|
||||
fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
|
||||
{{_, _, _} = jlib:datetime_string_to_timestamp(Data),
|
||||
|
@ -301,7 +316,8 @@ process_iq(From, To, IQ, SubEl, Fs) ->
|
|||
{'EXIT', _} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
|
||||
{Start, End, With, RSM} ->
|
||||
select_and_send(From, To, Start, End, With, RSM, IQ)
|
||||
select_and_send(LServer, From, To, Start, End,
|
||||
With, RSM, IQ, MsgType)
|
||||
end.
|
||||
|
||||
should_archive(#xmlel{name = <<"message">>} = Pkt) ->
|
||||
|
@ -310,11 +326,7 @@ should_archive(#xmlel{name = <<"message">>} = Pkt) ->
|
|||
{<<"error">>, _} ->
|
||||
false;
|
||||
{<<"groupchat">>, _} ->
|
||||
To = xml:get_attr_s(<<"to">>, Pkt#xmlel.attrs),
|
||||
case (jlib:string_to_jid(To))#jid.resource of
|
||||
<<"">> -> muc;
|
||||
_ -> false
|
||||
end;
|
||||
false;
|
||||
{_, <<>>} ->
|
||||
%% Empty body
|
||||
false;
|
||||
|
@ -370,43 +382,57 @@ should_archive_peer(C2SState,
|
|||
end
|
||||
end.
|
||||
|
||||
store0(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) ->
|
||||
case Type of
|
||||
muc -> store(C2SState, Pkt, Peer#jid.luser, LServer,
|
||||
jlib:jid_replace_resource(Peer, LUser), Type, Dir);
|
||||
true -> store(C2SState, Pkt, LUser, LServer, Peer, Type, Dir)
|
||||
end.
|
||||
should_archive_muc(_MUCState, _Peer) ->
|
||||
%% TODO
|
||||
true.
|
||||
|
||||
store(C2SState, Pkt, LUser, LServer, Peer, Type, Dir) ->
|
||||
store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
|
||||
Prefs = get_prefs(LUser, LServer),
|
||||
case should_archive_peer(C2SState, Prefs, Peer) of
|
||||
true ->
|
||||
do_store(Pkt, LUser, LServer, Peer, Type, Dir,
|
||||
US = {LUser, LServer},
|
||||
store(Pkt, LServer, US, chat, Peer, <<"">>, Dir,
|
||||
gen_mod:db_type(LServer, ?MODULE));
|
||||
false ->
|
||||
pass
|
||||
end.
|
||||
|
||||
do_store(Pkt, LUser, LServer, Peer, Type, _Dir, mnesia) ->
|
||||
store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
|
||||
case should_archive_muc(MUCState, Peer) of
|
||||
true ->
|
||||
LServer = MUCState#state.server_host,
|
||||
{U, S, _} = jlib:jid_tolower(RoomJID),
|
||||
store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv,
|
||||
gen_mod:db_type(LServer, ?MODULE));
|
||||
false ->
|
||||
pass
|
||||
end.
|
||||
|
||||
store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, mnesia) ->
|
||||
LPeer = {PUser, PServer, _} = jlib:jid_tolower(Peer),
|
||||
LServer2 = case Type of muc -> Peer#jid.lserver; _ -> LServer end,
|
||||
TS = now(),
|
||||
ID = jlib:integer_to_binary(now_to_usec(TS)),
|
||||
case mnesia:dirty_write(
|
||||
#archive_msg{us = {LUser, LServer2},
|
||||
#archive_msg{us = {LUser, LServer},
|
||||
id = ID,
|
||||
timestamp = TS,
|
||||
peer = LPeer,
|
||||
bare_peer = {PUser, PServer, <<>>},
|
||||
type = Type,
|
||||
nick = Nick,
|
||||
packet = Pkt}) of
|
||||
ok ->
|
||||
{ok, ID};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
do_store(Pkt, LUser, LServer, Peer, _Type, _Dir, odbc) ->
|
||||
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, odbc) ->
|
||||
TSinteger = now_to_usec(now()),
|
||||
ID = TS = jlib:integer_to_binary(TSinteger),
|
||||
SUser = case Type of
|
||||
chat -> LUser;
|
||||
groupchat -> jlib:jid_to_string({LUser, LHost, <<>>})
|
||||
end,
|
||||
BarePeer = jlib:jid_to_string(
|
||||
jlib:jid_tolower(
|
||||
jlib:jid_remove_resource(Peer))),
|
||||
|
@ -417,13 +443,15 @@ do_store(Pkt, LUser, LServer, Peer, _Type, _Dir, odbc) ->
|
|||
case ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
[<<"insert into archive (username, timestamp, "
|
||||
"peer, bare_peer, xml, txt) values (">>,
|
||||
<<"'">>, ejabberd_odbc:escape(LUser), <<"', ">>,
|
||||
"peer, bare_peer, xml, txt, kind, nick) values (">>,
|
||||
<<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>,
|
||||
<<"'">>, TS, <<"', ">>,
|
||||
<<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>,
|
||||
<<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>,
|
||||
<<"'">>, ejabberd_odbc:escape(XML), <<"', ">>,
|
||||
<<"'">>, ejabberd_odbc:escape(Body), <<"');">>]) of
|
||||
<<"'">>, ejabberd_odbc:escape(Body), <<"', ">>,
|
||||
<<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
|
||||
<<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of
|
||||
{updated, _} ->
|
||||
{ok, ID};
|
||||
Err ->
|
||||
|
@ -507,34 +535,37 @@ get_prefs(LUser, LServer, odbc) ->
|
|||
error
|
||||
end.
|
||||
|
||||
select_and_send(#jid{lserver = LServer} = From,
|
||||
To, Start, End, With, RSM, IQ) ->
|
||||
select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
|
||||
DBType = case gen_mod:db_type(LServer, ?MODULE) of
|
||||
odbc -> {odbc, LServer};
|
||||
DB -> DB
|
||||
end,
|
||||
select_and_send(From, To, Start, End, With, RSM, IQ,
|
||||
DBType).
|
||||
select_and_send(LServer, From, To, Start, End, With, RSM, IQ,
|
||||
MsgType, DBType).
|
||||
|
||||
select_and_send(From, To, Start, End, With, RSM, IQ, DBType) ->
|
||||
{Msgs, IsComplete, Count} = select_and_start(From, To, Start, End, With,
|
||||
RSM, DBType),
|
||||
select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType, DBType) ->
|
||||
{Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
|
||||
With, RSM, MsgType, DBType),
|
||||
SortedMsgs = lists:keysort(2, Msgs),
|
||||
send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
|
||||
|
||||
select_and_start(From, _To, StartUser, End, With, RSM, DB) ->
|
||||
{JidRequestor, Start, With2} = case With of
|
||||
{room, {LUserRoom, LServerRoom, <<>>} = WithJid} ->
|
||||
JR = jlib:make_jid(LUserRoom,LServerRoom,<<>>),
|
||||
St = StartUser,
|
||||
{JR, St, WithJid};
|
||||
select_and_start(LServer, From, To, Start, End, With, RSM, MsgType, DBType) ->
|
||||
case MsgType of
|
||||
chat ->
|
||||
case With of
|
||||
{room, {_, _, <<"">>} = WithJID} ->
|
||||
select(LServer, jlib:make_jid(WithJID), Start, End,
|
||||
WithJID, RSM, MsgType, DBType);
|
||||
_ ->
|
||||
{From, StartUser, With}
|
||||
end,
|
||||
select(JidRequestor, Start, End, With2, RSM, DB).
|
||||
select(LServer, From, Start, End,
|
||||
With, RSM, MsgType, DBType)
|
||||
end;
|
||||
{groupchat, _Role} ->
|
||||
select(LServer, To, Start, End, With, RSM, MsgType, DBType)
|
||||
end.
|
||||
|
||||
select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
|
||||
Start, End, With, RSM, mnesia) ->
|
||||
select(_LServer, #jid{luser = LUser, lserver = LServer} = JidRequestor,
|
||||
Start, End, With, RSM, MsgType, mnesia) ->
|
||||
MS = make_matchspec(LUser, LServer, Start, End, With),
|
||||
Msgs = mnesia:dirty_select(archive_msg, MS),
|
||||
{FilteredMsgs, IsComplete} = filter_by_rsm(Msgs, RSM),
|
||||
|
@ -543,12 +574,16 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
|
|||
fun(Msg) ->
|
||||
{Msg#archive_msg.id,
|
||||
jlib:binary_to_integer(Msg#archive_msg.id),
|
||||
msg_to_el(Msg, JidRequestor)}
|
||||
msg_to_el(Msg, MsgType, JidRequestor)}
|
||||
end, FilteredMsgs), IsComplete, Count};
|
||||
select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
|
||||
Start, End, With, RSM, {odbc, Host}) ->
|
||||
{Query, CountQuery} = make_sql_query(LUser, LServer,
|
||||
Start, End, With, RSM),
|
||||
select(LServer, #jid{luser = LUser} = JidRequestor,
|
||||
Start, End, With, RSM, MsgType, {odbc, Host}) ->
|
||||
User = case MsgType of
|
||||
chat -> LUser;
|
||||
{groupchat, _Role} -> jlib:jid_to_string(JidRequestor)
|
||||
end,
|
||||
{Query, CountQuery} = make_sql_query(User, LServer,
|
||||
Start, End, With, RSM),
|
||||
% XXX TODO from XEP-0313:
|
||||
% To conserve resources, a server MAY place a reasonable limit on
|
||||
% how many stanzas may be pushed to a client in one request. If a
|
||||
|
@ -573,24 +608,31 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor,
|
|||
{Res, true}
|
||||
end,
|
||||
{lists:map(
|
||||
fun([TS, XML, PeerBin]) ->
|
||||
fun([TS, XML, PeerBin, Kind, Nick]) ->
|
||||
#xmlel{} = El = xml_stream:parse_element(XML),
|
||||
Now = usec_to_now(jlib:binary_to_integer(TS)),
|
||||
PeerJid = jlib:jid_tolower(jlib:string_to_jid(PeerBin)),
|
||||
T = if Kind /= <<"">> ->
|
||||
jlib:binary_to_atom(Kind);
|
||||
true -> chat
|
||||
end,
|
||||
{TS, jlib:binary_to_integer(TS),
|
||||
msg_to_el(#archive_msg{timestamp = Now,
|
||||
packet = El,
|
||||
type = T,
|
||||
nick = Nick,
|
||||
peer = PeerJid},
|
||||
MsgType,
|
||||
JidRequestor)}
|
||||
end, Res1), IsComplete, jlib:binary_to_integer(Count)};
|
||||
_ ->
|
||||
{[], false, 0}
|
||||
end.
|
||||
|
||||
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer},
|
||||
JidRequestor) ->
|
||||
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
|
||||
MsgType, JidRequestor) ->
|
||||
Delay = jlib:now_to_utc_string(TS),
|
||||
Pkt = maybe_update_from_to(Pkt1, JidRequestor, Peer),
|
||||
Pkt = maybe_update_from_to(Pkt1, JidRequestor, Peer, MsgType, Nick),
|
||||
#xmlel{name = <<"forwarded">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
|
||||
children = [#xmlel{name = <<"delay">>,
|
||||
|
@ -599,9 +641,9 @@ msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer},
|
|||
xml:replace_tag_attr(
|
||||
<<"xmlns">>, <<"jabber:client">>, Pkt)]}.
|
||||
|
||||
maybe_update_from_to(Pkt, _JIDRequestor, undefined) ->
|
||||
maybe_update_from_to(Pkt, _JIDRequestor, undefined, _Type, _Nick) ->
|
||||
Pkt;
|
||||
maybe_update_from_to(Pkt, JidRequestor, Peer) ->
|
||||
maybe_update_from_to(Pkt, JidRequestor, Peer, chat, _Nick) ->
|
||||
case xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
|
||||
<<"groupchat">> ->
|
||||
Pkt2 = xml:replace_tag_attr(<<"to">>,
|
||||
|
@ -610,7 +652,23 @@ maybe_update_from_to(Pkt, JidRequestor, Peer) ->
|
|||
xml:replace_tag_attr(<<"from">>, jlib:jid_to_string(Peer),
|
||||
Pkt2);
|
||||
_ -> Pkt
|
||||
end.
|
||||
end;
|
||||
maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor,
|
||||
Peer, {groupchat, Role}, Nick) ->
|
||||
Items = case Role of
|
||||
moderator ->
|
||||
[#xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_MUC_ADMIN}],
|
||||
children =
|
||||
[#xmlel{name = <<"item">>,
|
||||
attrs = [{<<"jid">>,
|
||||
jlib:jid_to_string(Peer)}]}]}];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Pkt1 = Pkt#xmlel{children = Items ++ Els},
|
||||
Pkt2 = jlib:replace_from(jlib:jid_replace_resource(JidRequestor, Nick), Pkt1),
|
||||
jlib:remove_attr(<<"to">>, Pkt2).
|
||||
|
||||
send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
|
||||
QID = xml:get_tag_attr_s(<<"queryid">>, SubEl),
|
||||
|
@ -737,7 +795,7 @@ make_matchspec(LUser, LServer, Start, End, none) ->
|
|||
Msg
|
||||
end).
|
||||
|
||||
make_sql_query(LUser, _LServer, Start, End, With, RSM) ->
|
||||
make_sql_query(User, _LServer, Start, End, With, RSM) ->
|
||||
{Max, Direction, ID} = case RSM of
|
||||
#rsm_in{} ->
|
||||
{RSM#rsm_in.max,
|
||||
|
@ -795,9 +853,9 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) ->
|
|||
_ ->
|
||||
[]
|
||||
end,
|
||||
SUser = ejabberd_odbc:escape(LUser),
|
||||
SUser = ejabberd_odbc:escape(User),
|
||||
|
||||
Query = [<<"SELECT timestamp, xml, peer"
|
||||
Query = [<<"SELECT timestamp, xml, peer, kind, nick"
|
||||
" FROM archive WHERE username='">>,
|
||||
SUser, <<"'">>, WithClause, StartClause, EndClause,
|
||||
PageClause],
|
||||
|
@ -808,7 +866,7 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) ->
|
|||
% ID can be empty because of
|
||||
% XEP-0059: Result Set Management
|
||||
% 2.5 Requesting the Last Page in a Result Set
|
||||
[<<"SELECT timestamp, xml, peer FROM (">>, Query,
|
||||
[<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query,
|
||||
<<" ORDER BY timestamp DESC ">>,
|
||||
LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
|
||||
_ ->
|
||||
|
|
|
@ -345,6 +345,7 @@ init([Host, Opts]) ->
|
|||
persistent -> Bool;
|
||||
public -> Bool;
|
||||
public_list -> Bool;
|
||||
mam -> Bool;
|
||||
password -> fun iolist_to_binary/1;
|
||||
title -> fun iolist_to_binary/1;
|
||||
allow_private_messages_from_visitors ->
|
||||
|
@ -524,7 +525,8 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
|||
attrs =
|
||||
[{<<"xmlns">>, XMLNS}],
|
||||
children =
|
||||
iq_disco_info(Lang) ++
|
||||
iq_disco_info(
|
||||
ServerHost, Lang) ++
|
||||
Info}]},
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(Res));
|
||||
|
@ -767,7 +769,7 @@ register_room(Host, Room, Pid) ->
|
|||
mnesia:transaction(F).
|
||||
|
||||
|
||||
iq_disco_info(Lang) ->
|
||||
iq_disco_info(ServerHost, Lang) ->
|
||||
[#xmlel{name = <<"identity">>,
|
||||
attrs =
|
||||
[{<<"category">>, <<"conference">>},
|
||||
|
@ -788,7 +790,14 @@ iq_disco_info(Lang) ->
|
|||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_RSM}], children = []},
|
||||
#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_VCARD}], children = []}].
|
||||
attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
|
||||
case gen_mod:is_loaded(ServerHost, mod_mam) of
|
||||
true ->
|
||||
[#xmlel{name = <<"feature">>,
|
||||
attrs = [{<<"var">>, ?NS_MAM_0}]}];
|
||||
false ->
|
||||
[]
|
||||
end.
|
||||
|
||||
iq_disco_items(Host, From, Lang, none) ->
|
||||
lists:zf(fun (#muc_online_room{name_host =
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
start_link/7,
|
||||
start/9,
|
||||
start/7,
|
||||
get_role/2,
|
||||
route/4]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
|
@ -426,56 +427,67 @@ normal_state({route, From, <<"">>,
|
|||
#xmlel{name = <<"iq">>} = Packet},
|
||||
StateData) ->
|
||||
case jlib:iq_query_info(Packet) of
|
||||
#iq{type = Type, xmlns = XMLNS, lang = Lang,
|
||||
sub_el = #xmlel{name = SubElName} = SubEl} =
|
||||
IQ
|
||||
when (XMLNS == (?NS_MUC_ADMIN)) or
|
||||
(XMLNS == (?NS_MUC_OWNER))
|
||||
or (XMLNS == (?NS_DISCO_INFO))
|
||||
or (XMLNS == (?NS_DISCO_ITEMS))
|
||||
or (XMLNS == (?NS_VCARD))
|
||||
or (XMLNS == (?NS_CAPTCHA)) ->
|
||||
Res1 = case XMLNS of
|
||||
?NS_MUC_ADMIN ->
|
||||
process_iq_admin(From, Type, Lang, SubEl, StateData);
|
||||
?NS_MUC_OWNER ->
|
||||
process_iq_owner(From, Type, Lang, SubEl, StateData);
|
||||
?NS_DISCO_INFO ->
|
||||
process_iq_disco_info(From, Type, Lang, StateData);
|
||||
?NS_DISCO_ITEMS ->
|
||||
process_iq_disco_items(From, Type, Lang, StateData);
|
||||
?NS_VCARD ->
|
||||
process_iq_vcard(From, Type, Lang, SubEl, StateData);
|
||||
?NS_CAPTCHA ->
|
||||
process_iq_captcha(From, Type, Lang, SubEl, StateData)
|
||||
end,
|
||||
{IQRes, NewStateData} = case Res1 of
|
||||
{result, Res, SD} ->
|
||||
{IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = SubElName,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
XMLNS}],
|
||||
children = Res}]},
|
||||
SD};
|
||||
{error, Error} ->
|
||||
{IQ#iq{type = error,
|
||||
sub_el = [SubEl, Error]},
|
||||
StateData}
|
||||
end,
|
||||
ejabberd_router:route(StateData#state.jid, From,
|
||||
jlib:iq_to_xml(IQRes)),
|
||||
case NewStateData of
|
||||
stop -> {stop, normal, StateData};
|
||||
_ -> {next_state, normal_state, NewStateData}
|
||||
end;
|
||||
reply -> {next_state, normal_state, StateData};
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
ejabberd_router:route(StateData#state.jid, From, Err),
|
||||
{next_state, normal_state, StateData}
|
||||
reply ->
|
||||
{next_state, normal_state, StateData};
|
||||
IQ0 ->
|
||||
case ejabberd_hooks:run_fold(
|
||||
muc_process_iq,
|
||||
StateData#state.server_host,
|
||||
IQ0, [StateData, From, StateData#state.jid]) of
|
||||
ignore ->
|
||||
{next_state, normal_state, StateData};
|
||||
#iq{type = T} = IQRes when T == error; T == result ->
|
||||
ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)),
|
||||
{next_state, normal_state, StateData};
|
||||
#iq{type = Type, xmlns = XMLNS, lang = Lang,
|
||||
sub_el = #xmlel{name = SubElName} = SubEl} = IQ
|
||||
when (XMLNS == (?NS_MUC_ADMIN)) or
|
||||
(XMLNS == (?NS_MUC_OWNER))
|
||||
or (XMLNS == (?NS_DISCO_INFO))
|
||||
or (XMLNS == (?NS_DISCO_ITEMS))
|
||||
or (XMLNS == (?NS_VCARD))
|
||||
or (XMLNS == (?NS_CAPTCHA)) ->
|
||||
Res1 = case XMLNS of
|
||||
?NS_MUC_ADMIN ->
|
||||
process_iq_admin(From, Type, Lang, SubEl, StateData);
|
||||
?NS_MUC_OWNER ->
|
||||
process_iq_owner(From, Type, Lang, SubEl, StateData);
|
||||
?NS_DISCO_INFO ->
|
||||
process_iq_disco_info(From, Type, Lang, StateData);
|
||||
?NS_DISCO_ITEMS ->
|
||||
process_iq_disco_items(From, Type, Lang, StateData);
|
||||
?NS_VCARD ->
|
||||
process_iq_vcard(From, Type, Lang, SubEl, StateData);
|
||||
?NS_CAPTCHA ->
|
||||
process_iq_captcha(From, Type, Lang, SubEl, StateData)
|
||||
end,
|
||||
{IQRes, NewStateData} =
|
||||
case Res1 of
|
||||
{result, Res, SD} ->
|
||||
{IQ#iq{type = result,
|
||||
sub_el =
|
||||
[#xmlel{name = SubElName,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
XMLNS}],
|
||||
children = Res}]},
|
||||
SD};
|
||||
{error, Error} ->
|
||||
{IQ#iq{type = error,
|
||||
sub_el = [SubEl, Error]},
|
||||
StateData}
|
||||
end,
|
||||
ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)),
|
||||
case NewStateData of
|
||||
stop -> {stop, normal, StateData};
|
||||
_ -> {next_state, normal_state, NewStateData}
|
||||
end;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
ejabberd_router:route(StateData#state.jid, From, Err),
|
||||
{next_state, normal_state, StateData}
|
||||
end
|
||||
end;
|
||||
normal_state({route, From, Nick,
|
||||
#xmlel{name = <<"presence">>} = Packet},
|
||||
|
@ -962,11 +974,11 @@ process_groupchat_message(From,
|
|||
FromNick),
|
||||
StateData#state.server_host,
|
||||
StateData#state.users,
|
||||
Packet),
|
||||
NewPacket),
|
||||
NewStateData2 = case has_body_or_subject(Packet) of
|
||||
true ->
|
||||
add_message_to_history(FromNick, From,
|
||||
Packet,
|
||||
NewPacket,
|
||||
NewStateData1);
|
||||
false ->
|
||||
NewStateData1
|
||||
|
@ -3531,6 +3543,13 @@ get_config(Lang, StateData, From) ->
|
|||
<<"captcha_protected">>,
|
||||
(Config#config.captcha_protected))];
|
||||
false -> []
|
||||
end ++
|
||||
case gen_mod:is_loaded(StateData#state.server_host, mod_mam) of
|
||||
true ->
|
||||
[?BOOLXFIELD(<<"Enable message archiving">>,
|
||||
<<"muc#roomconfig_mam">>,
|
||||
(Config#config.mam))];
|
||||
false -> []
|
||||
end
|
||||
++
|
||||
[?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
|
||||
|
@ -3740,6 +3759,8 @@ set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]}
|
|||
| Opts],
|
||||
Config) ->
|
||||
?SET_BOOL_XOPT(logging, Val);
|
||||
set_xoption([{<<"muc#roomconfig_mam">>, [Val]}|Opts], Config) ->
|
||||
?SET_BOOL_XOPT(mam, Val);
|
||||
set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
|
||||
Vals}
|
||||
| Opts],
|
||||
|
@ -3902,6 +3923,9 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
|||
StateData#state{config =
|
||||
(StateData#state.config)#config{logging =
|
||||
Val}};
|
||||
mam ->
|
||||
StateData#state{config =
|
||||
(StateData#state.config)#config{mam = Val}};
|
||||
captcha_whitelist ->
|
||||
StateData#state{config =
|
||||
(StateData#state.config)#config{captcha_whitelist
|
||||
|
|
Loading…
Reference in New Issue