Skip to content

Commit

Permalink
Add an example for send_message callback
Browse files Browse the repository at this point in the history
  • Loading branch information
arcusfelis committed Apr 23, 2021
1 parent 50fb498 commit 2787246
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 11 deletions.
1 change: 1 addition & 0 deletions big_tests/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
{suites, "tests", websockets_SUITE}.
{suites, "tests", xep_0352_csi_SUITE}.
{suites, "tests", domain_isolation_SUITE}.
{suites, "tests", mam_send_message_SUITE}.

{config, ["test.config"]}.
{logdir, "ct_report"}.
Expand Down
142 changes: 142 additions & 0 deletions big_tests/tests/mam_send_message_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
-module(mam_send_message_SUITE).

%% API
-export([all/0,
groups/0,
init_per_suite/1,
end_per_suite/1,
init_per_group/2,
end_per_group/2,
init_per_testcase/2,
end_per_testcase/2]).

-export([mam_muc_send_message/1,
mam_pm_send_message/1]).

-import(mam_helper,
[stanza_archive_request/2,
wait_archive_respond/1,
assert_respond_size/2,
respond_messages/1,
parse_forwarded_message/1]).

-import(distributed_helper, [mim/0,
require_rpc_nodes/1,
rpc/4]).

-include("mam_helper.hrl").
-include_lib("escalus/include/escalus.hrl").
-include_lib("escalus/include/escalus_xmlns.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("exml/include/exml_stream.hrl").

all() ->
[{group, send_message}].

groups() ->
[
{send_message, [], [mam_pm_send_message,
mam_muc_send_message]}
].

domain() ->
ct:get_config({hosts, mim, domain}).

%%%===================================================================
%%% Overall setup/teardown
%%%===================================================================
init_per_suite(Config) ->
escalus:init_per_suite(Config).

end_per_suite(Config) ->
escalus:end_per_suite(Config).

%%%===================================================================
%%% Group specific setup/teardown
%%%===================================================================
init_per_group(Group, Config) ->
case mongoose_helper:is_rdbms_enabled(domain()) of
true ->
load_custom_module(),
Config2 = dynamic_modules:save_modules(domain(), Config),
rpc(mim(), gen_mod_deps, start_modules, [domain(), group_to_modules(Group)]),
Config2;
false ->
{skip, require_rdbms}
end.

end_per_group(_Groupname, Config) ->
case mongoose_helper:is_rdbms_enabled(domain()) of
true ->
dynamic_modules:restore_modules(domain(), Config);
false ->
ok
end,
ok.

group_to_modules(send_message) ->
MH = muc_light_helper:muc_host(),
[{mod_mam_meta, [{backend, rdbms}, {pm, []}, {muc, [{host, MH}]},
{send_message, mam_send_message_example}]},
{mod_muc_light, []},
{mam_send_message_example, []}].

load_custom_module() ->
mam_send_message_example:module_info(),
{Mod, Code, File} = code:get_object_code(mam_send_message_example),
rpc(mim(), code, load_binary, [Mod, File, Code]).

%%%===================================================================
%%% Testcase specific setup/teardown
%%%===================================================================

init_per_testcase(TestCase, Config) ->
escalus:init_per_testcase(TestCase, Config).

end_per_testcase(TestCase, Config) ->
escalus:end_per_testcase(TestCase, Config).

%%%===================================================================
%%% Test Cases
%%%===================================================================

mam_pm_send_message(Config) ->
P = ?config(props, Config),
F = fun(Alice, Bob) ->
escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)),
escalus:wait_for_stanza(Bob),
mam_helper:wait_for_archive_size(Alice, 1),
mam_helper:wait_for_archive_size(Bob, 1),
escalus:send(Alice, stanza_archive_request(P, <<"q1">>)),
Res = wait_archive_respond(Alice),
assert_respond_size(1, Res),
[Msg] = respond_messages(Res),
verify_has_some_hash(Msg)
end,
escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F).

mam_muc_send_message(Config0) ->
F = fun(Config, Alice) ->
P = ?config(props, Config),
Room = muc_helper:fresh_room_name(),
MucHost = muc_light_helper:muc_host(),
muc_light_helper:create_room(Room, MucHost, alice,
[], Config, muc_light_helper:ver(1)),
escalus_assert:has_no_stanzas(Alice),
RoomAddr = <<Room/binary, "@", MucHost/binary>>,
escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, <<"text">>)),
M = escalus:wait_for_stanza(Alice),
escalus:assert(is_message, M),
escalus_assert:has_no_stanzas(Alice),
mam_helper:wait_for_room_archive_size(MucHost, Room, 1),
escalus:send(Alice, escalus_stanza:to(stanza_archive_request(P, <<"q1">>), RoomAddr)),
[Msg] = respond_messages(assert_respond_size(1, wait_archive_respond(Alice))),
verify_has_some_hash(Msg)
end,
escalus_fresh:story_with_config(Config0, [{alice, 1}], F).

verify_has_some_hash(Msg) ->
Hash = exml_query:path(Msg, [{element, <<"result">>},
{element, <<"some_hash">>},
{attr, <<"value">>}]),
binary_to_integer(Hash). %% is integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
%% Adds some_hash element to each extracted message result.
%%
%% An example module for extending MAM lookup results.
%% Defines a callback for send_message callback.
%% Handles lookup messages hooks to extend message rows with extra info.
-module(mam_send_message_example).
-behaviour(gen_mod).
-behaviour(mongoose_module_metrics).
-include_lib("exml/include/exml.hrl").

-export([start/2,
stop/1,
lookup_messages/3,
send_message/4]).


start(Host, _Opts) ->
ejabberd_hooks:add(hooks(Host)).

stop(Host) ->
ejabberd_hooks:delete(hooks(Host)).

hooks(Host) ->
[{mam_lookup_messages, Host, ?MODULE, lookup_messages, 60},
{mam_muc_lookup_messages, Host, ?MODULE, lookup_messages, 60}].

lookup_messages({error, _Reason} = Result, _Host, _Params) ->
Result;
lookup_messages({ok, {TotalCount, Offset, MessageRows}},
Host, Params = #{owner_jid := ArcJID}) ->
MessageRows2 = [extend_message(Host, ArcJID, Row) || Row <- MessageRows],
{ok, {TotalCount, Offset, MessageRows2}}.

extend_message(_Host, _ArcJID, Row = #{}) ->
%% Extend a message with a new field
%% Usually extracted from a DB
Row#{some_hash => erlang:phash2(Row, 32)}.

send_message(Row, From, To, Mess) ->
Res = xml:get_subtag(Mess, <<"result">>),
Res2 = xml:append_subtags(Res, [new_subelem(Row)]),
Mess2 = xml:replace_subelement(Mess, Res2),
mod_mam_utils:send_message(Row, From, To, Mess2).

new_subelem(#{some_hash := SomeHash}) ->
#xmlel{name = <<"some_hash">>, attrs = [{<<"value">>, integer_to_binary(SomeHash)}]}.
3 changes: 3 additions & 0 deletions doc/modules/mod_mam.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ Name of a module implementing [`is_archivable_message/3` callback](#is_archivabl
Name of a module implementing `send_message/4` callback that routes a message during lookup operation.
Consult with `mod_mam_utils:send_message/4` code for more information.

Check `big_tests/tests/mam_send_message_SUITE_data/mam_send_message_example.erl` file
in the MongooseIM repository for the usage example.

### `modules.mod_mam_meta.archive_chat_markers`
* **Syntax:** boolean
* **Default:** `false`
Expand Down
12 changes: 2 additions & 10 deletions src/mod_muc_room.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4321,18 +4321,10 @@ send_decline_invitation({Packet, XEl, DEl, ToJID}, RoomJID, FromJID) ->
DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs),
DAttrs3 = [{<<"from">>, FromString} | DAttrs2],
DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3, children = DEls},
XEl2 = replace_subelement(XEl, DEl2),
Packet2 = replace_subelement(Packet, XEl2),
XEl2 = xml:replace_subelement(XEl, DEl2),
Packet2 = xml:replace_subelement(Packet, XEl2),
ejabberd_router:route(RoomJID, ToJID, Packet2).

%% @doc Given an element and a new subelement,
%% replace the instance of the subelement in element with the new subelement.
-spec replace_subelement(exml:element(), exml:element()) -> exml:element().
replace_subelement(XE = #xmlel{children = SubEls}, NewSubEl) ->
{_, NameNewSubEl, _, _} = NewSubEl,
SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl),
XE#xmlel{children = SubEls2}.

-spec send_error_only_occupants(binary(), exml:element(),
binary() | nonempty_string(),
jid:jid(), jid:jid()) -> mongoose_acc:t().
Expand Down
10 changes: 9 additions & 1 deletion src/xml.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
get_subtag/2,
append_subtags/2,
get_path_s/2,
replace_tag_attr/3]).
replace_tag_attr/3,
replace_subelement/2]).

-include("mongoose.hrl").
-include("jlib.hrl").
Expand Down Expand Up @@ -144,6 +145,13 @@ replace_tag_attr(Attr, Value, XE = #xmlel{attrs = Attrs}) ->
Attrs2 = [{Attr, Value} | Attrs1],
XE#xmlel{attrs = Attrs2}.

%% @doc Given an element and a new subelement,
%% replace the instance of the subelement in element with the new subelement.
-spec replace_subelement(exml:element(), exml:element()) -> exml:element().
replace_subelement(XE = #xmlel{children = SubEls}, NewSubEl) ->
{_, NameNewSubEl, _, _} = NewSubEl,
SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl),
XE#xmlel{children = SubEls2}.

-spec context_default(binary() | string()) -> <<>> | [].
context_default(Attr) when is_list(Attr) ->
Expand Down

0 comments on commit 2787246

Please sign in to comment.