diff --git a/big_tests/default.spec b/big_tests/default.spec index 956670df95..4bb8b9f247 100644 --- a/big_tests/default.spec +++ b/big_tests/default.spec @@ -85,6 +85,7 @@ {suites, "tests", service_domain_db_SUITE}. {suites, "tests", domain_isolation_SUITE}. {suites, "tests", domain_removal_SUITE}. +{suites, "tests", mam_send_message_SUITE}. {config, ["test.config"]}. {logdir, "ct_report"}. diff --git a/big_tests/tests/mam_send_message_SUITE.erl b/big_tests/tests/mam_send_message_SUITE.erl new file mode 100644 index 0000000000..174fbd362b --- /dev/null +++ b/big_tests/tests/mam_send_message_SUITE.erl @@ -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)]), + [{props, mam_helper:mam06_props()}|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 = <>, + 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 diff --git a/big_tests/tests/mam_send_message_SUITE_data/mam_send_message_example.erl b/big_tests/tests/mam_send_message_SUITE_data/mam_send_message_example.erl new file mode 100644 index 0000000000..4801645218 --- /dev/null +++ b/big_tests/tests/mam_send_message_SUITE_data/mam_send_message_example.erl @@ -0,0 +1,47 @@ +%% 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}]. + +%% caller_jid could be used for privacy checking or per-user customization +lookup_messages({error, _Reason} = Result, _Host, _Params) -> + Result; +lookup_messages({ok, {TotalCount, Offset, MessageRows}}, + Host, Params = #{owner_jid := ArcJID, caller_jid := _CallerJID}) -> + 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)}]}. diff --git a/doc/modules/mod_mam.md b/doc/modules/mod_mam.md index 969230d8ef..af5ac231db 100644 --- a/doc/modules/mod_mam.md +++ b/doc/modules/mod_mam.md @@ -66,6 +66,17 @@ Do not add a `` element from MAM v0.6. Name of a module implementing [`is_archivable_message/3` callback](#is_archivable_message) that determines if the message should be archived. +### `modules.mod_mam_meta.send_message` +* **Syntax:** non-empty string +* **Default:** `"mod_mam_utils"` +* **Example:** `send_message = "mod_mam_utils"` + +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` diff --git a/src/mam/mam_decoder.erl b/src/mam/mam_decoder.erl index 77b508041d..c267bf3428 100644 --- a/src/mam/mam_decoder.erl +++ b/src/mam/mam_decoder.erl @@ -17,14 +17,14 @@ decode_row({ExtMessID, ExtSrcJID, ExtData}, Env) -> MessID = mongoose_rdbms:result_to_integer(ExtMessID), SrcJID = decode_jid(ExtSrcJID, Env), Packet = decode_packet(ExtData, Env), - {MessID, SrcJID, Packet}. + #{id => MessID, jid => SrcJID, packet => Packet}. -spec decode_muc_row(db_muc_row(), env_vars()) -> mod_mam:message_row(). decode_muc_row({ExtMessID, Nick, ExtData}, Env = #{archive_jid := RoomJID}) -> MessID = mongoose_rdbms:result_to_integer(ExtMessID), SrcJID = jid:replace_resource(RoomJID, Nick), Packet = decode_packet(ExtData, Env), - {MessID, SrcJID, Packet}. + #{id => MessID, jid => SrcJID, packet => Packet}. -spec decode_muc_gdpr_row(db_muc_gdpr_row(), env_vars()) -> decoded_muc_gdpr_row(). decode_muc_gdpr_row({ExtMessID, ExtData}, Env) -> diff --git a/src/mam/mam_iq.erl b/src/mam/mam_iq.erl index 7a170cfe1f..d75080dfbe 100644 --- a/src/mam/mam_iq.erl +++ b/src/mam/mam_iq.erl @@ -15,7 +15,7 @@ -export([form_to_with_jid/1]). -export([form_to_lookup_params/4]). --export([lookup_params_with_archive_details/3]). +-export([lookup_params_with_archive_details/4]). -import(mod_mam_utils, [maybe_microseconds/1, @@ -34,6 +34,7 @@ -type lookup_params() :: #{ archive_id => mod_mam:archive_id(), owner_jid => jid:jid(), + caller_jid => jid:jid(), rsm => jlib:rsm_in() | undefined, max_result_limit => non_neg_integer(), %% Contains page size value provided by client or enforced by server. @@ -215,11 +216,12 @@ common_lookup_params(QueryEl, MaxResultLimit, DefaultResultLimit) -> limit_passed => Limit =/= <<>>, ordering_direction => ordering_direction(RSM)}. --spec lookup_params_with_archive_details(lookup_params(), term(), jid:jid()) -> +-spec lookup_params_with_archive_details(lookup_params(), term(), jid:jid(), jid:jid()) -> lookup_params(). -lookup_params_with_archive_details(Params, ArcID, ArcJID) -> +lookup_params_with_archive_details(Params, ArcID, ArcJID, CallerJID) -> Params#{archive_id => ArcID, - owner_jid => ArcJID}. + owner_jid => ArcJID, + caller_jid => CallerJID}. ordering_direction(#rsm_in{direction = before}) -> backward; ordering_direction(_) -> forward. diff --git a/src/mam/mam_lookup.erl b/src/mam/mam_lookup.erl index f25db76fa7..74a397c8cd 100644 --- a/src/mam/mam_lookup.erl +++ b/src/mam/mam_lookup.erl @@ -157,8 +157,8 @@ rsm_to_regular_lookup_vars(RSM, Filter, Offset, PageSize) -> decode_rows(MessageRows, Env) -> [decode_row(Row, Env) || Row <- MessageRows]. -%% First element of the tuple is decoded message ID -decoded_row_to_message_id(DecodedRow) -> element(1, DecodedRow). +-spec decoded_row_to_message_id(mod_mam:message_row()) -> mod_mam:message_id(). +decoded_row_to_message_id(#{id := MessId}) -> MessId. -spec extract_messages(Env :: env_vars(), Filter :: filter(), Offset :: non_neg_integer(), Max :: pos_integer(), diff --git a/src/mam/mam_send_message.erl b/src/mam/mam_send_message.erl new file mode 100644 index 0000000000..990fe8f43c --- /dev/null +++ b/src/mam/mam_send_message.erl @@ -0,0 +1,19 @@ +%% Forwards extracted messages to the client. +%% The default implementation is in mod_mam_utils. +-module(mam_send_message). +-export([call_send_message/5]). + +-callback send_message( + Row :: mod_mam:message_row(), + ArcJID :: jid:jid(), + From :: jid:jid(), + Packet :: exml:element()) -> Acc :: mongoose_acc:t(). + +-spec call_send_message( + SendModule :: module(), + Row :: mod_mam:message_row(), + ArcJID :: jid:jid(), + From :: jid:jid(), + Packet :: exml:element()) -> Acc :: mongoose_acc:t(). +call_send_message(SendModule, Row, ArcJID, From, Packet) -> + SendModule:send_message(Row, ArcJID, From, Packet). diff --git a/src/mam/mod_mam.erl b/src/mam/mod_mam.erl index 48519c2b44..142d405159 100644 --- a/src/mam/mod_mam.erl +++ b/src/mam/mod_mam.erl @@ -95,8 +95,7 @@ %% ejabberd -import(mod_mam_utils, - [send_message/3, - is_jid_in_user_roster/2]). + [is_jid_in_user_roster/2]). -include("mongoose.hrl"). @@ -119,7 +118,7 @@ -type borders() :: #mam_borders{}. --type message_row() :: {message_id(), jid:jid(), exml:element()}. +-type message_row() :: #{id := message_id(), jid := jid:jid(), packet := exml:element()}. -type lookup_result() :: {TotalCount :: non_neg_integer() | undefined, Offset :: non_neg_integer() | undefined, MessageRows :: [message_row()]}. @@ -425,7 +424,7 @@ handle_set_message_form(#jid{} = From, #jid{} = ArcJID, Params0 = mam_iq:form_to_lookup_params(IQ, mod_mam_params:max_result_limit(?MODULE, Host), mod_mam_params:default_result_limit(?MODULE, Host), mod_mam_params:extra_params_module(?MODULE, Host)), - Params = mam_iq:lookup_params_with_archive_details(Params0, ArcID, ArcJID), + Params = mam_iq:lookup_params_with_archive_details(Params0, ArcID, ArcJID, From), case lookup_messages(Host, Params) of {error, Reason} -> report_issue(Reason, mam_lookup_failed, ArcJID, IQ), @@ -450,12 +449,16 @@ forward_messages(From, ArcJID, MamNs, QueryID, MessageRows, SetClientNs) -> [_|_] -> {message_row_to_ext_id(hd(MessageRows)), message_row_to_ext_id(lists:last(MessageRows))} end, - - [send_message(ArcJID, From, - message_row_to_xml(MamNs, M, QueryID, SetClientNs)) - || M <- MessageRows], + Host = ArcJID#jid.lserver, + SendModule = mod_mam_params:send_message_mod(?MODULE, Host), + [send_message(SendModule, Row, ArcJID, From, + message_row_to_xml(MamNs, Row, QueryID, SetClientNs)) + || Row <- MessageRows], {FirstMessID, LastMessID}. +send_message(SendModule, Row, ArcJID, From, Packet) -> + mam_send_message:call_send_message(SendModule, Row, ArcJID, From, Packet). + -spec handle_get_message_form(jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq(). handle_get_message_form(_From=#jid{lserver = Host}, _ArcJID=#jid{}, IQ=#iq{}) -> @@ -609,20 +612,19 @@ archive_message(Host, Params) -> %% ---------------------------------------------------------------------- %% Helpers --type messid_jid_packet() :: {MessId :: integer(), - SrcJID :: jid:jid(), - Packet :: exml:element()}. --spec message_row_to_xml(binary(), messid_jid_packet(), QueryId :: binary(), boolean()) -> +-spec message_row_to_xml(binary(), message_row(), QueryId :: binary(), boolean()) -> exml:element(). -message_row_to_xml(MamNs, {MessID, SrcJID, Packet}, QueryID, SetClientNs) -> +message_row_to_xml(MamNs, #{id := MessID, jid := SrcJID, packet := Packet}, + QueryID, SetClientNs) -> {Microseconds, _NodeMessID} = decode_compact_uuid(MessID), - TS = calendar:system_time_to_rfc3339(erlang:convert_time_unit(Microseconds, microsecond, second), [{offset, "Z"}]), + Secs = erlang:convert_time_unit(Microseconds, microsecond, second), + TS = calendar:system_time_to_rfc3339(Secs, [{offset, "Z"}]), BExtMessID = mess_id_to_external_binary(MessID), Packet1 = mod_mam_utils:maybe_set_client_xmlns(SetClientNs, Packet), wrap_message(MamNs, Packet1, QueryID, BExtMessID, TS, SrcJID). --spec message_row_to_ext_id(messid_jid_packet()) -> binary(). -message_row_to_ext_id({MessID, _, _}) -> +-spec message_row_to_ext_id(message_row()) -> binary(). +message_row_to_ext_id(#{id := MessID}) -> mess_id_to_external_binary(MessID). handle_error_iq(Host, Acc, _To, _Action, {error, _Reason, IQ}) -> diff --git a/src/mam/mod_mam_cassandra_arch.erl b/src/mam/mod_mam_cassandra_arch.erl index 7224090eac..4d0db3f8f1 100644 --- a/src/mam/mod_mam_cassandra_arch.erl +++ b/src/mam/mod_mam_cassandra_arch.erl @@ -430,7 +430,7 @@ rows_to_uniform_format(MessageRows) -> row_to_uniform_format(#{from_jid := FromJID, message := Msg, id := MsgID}) -> SrcJID = jid:from_binary(FromJID), Packet = stored_binary_to_packet(Msg), - {MsgID, SrcJID, Packet}. + #{id => MsgID, jid => SrcJID, packet => Packet}. row_to_message_id(#{id := MsgID}) -> MsgID. diff --git a/src/mam/mod_mam_elasticsearch_arch.erl b/src/mam/mod_mam_elasticsearch_arch.erl index 50d389b814..c20ee1dcd5 100644 --- a/src/mam/mod_mam_elasticsearch_arch.erl +++ b/src/mam/mod_mam_elasticsearch_arch.erl @@ -274,14 +274,13 @@ search_result_to_mam_lookup_result(Result, Params) -> {CorrectedTotalCount, Offset, Messages} end. --spec hit_to_mam_message(map()) -> {mod_mam:message_id(), jid:jid(), exml:element()}. +-spec hit_to_mam_message(map()) -> mod_mam:message_row(). hit_to_mam_message(#{<<"_source">> := JSON}) -> MessageId = maps:get(<<"mam_id">>, JSON), Packet = maps:get(<<"message">>, JSON), SourceBinJid = maps:get(<<"source_jid">>, JSON), - {ok, Stanza} = exml:parse(Packet), - {MessageId, jid:from_binary(SourceBinJid), Stanza}. + #{id => MessageId, jid => jid:from_binary(SourceBinJid), packet => Stanza}. hit_to_gdpr_mam_message(#{<<"_source">> := JSON}) -> MessageId = maps:get(<<"mam_id">>, JSON), diff --git a/src/mam/mod_mam_meta.erl b/src/mam/mod_mam_meta.erl index 857a47d6e8..158807f54c 100644 --- a/src/mam/mod_mam_meta.erl +++ b/src/mam/mod_mam_meta.erl @@ -56,6 +56,8 @@ config_items() -> <<"no_stanzaid_element">> => #option{type = boolean}, <<"is_archivable_message">> => #option{type = atom, validate = module}, + <<"send_message">> => #option{type = atom, + validate = module}, <<"archive_chat_markers">> => #option{type = boolean}, <<"message_retraction">> => #option{type = boolean}, @@ -202,6 +204,7 @@ valid_core_mod_opts(mod_mam_muc) -> common_opts() -> [is_archivable_message, + send_message, archive_chat_markers, extra_lookup_params, full_text_search, diff --git a/src/mam/mod_mam_muc.erl b/src/mam/mod_mam_muc.erl index ba128f8f16..76761e2c08 100644 --- a/src/mam/mod_mam_muc.erl +++ b/src/mam/mod_mam_muc.erl @@ -90,11 +90,6 @@ [mess_id_to_external_binary/1, is_complete_result_page/4]). -%% ejabberd --import(mod_mam_utils, - [send_message/3]). - - -include_lib("mongoose.hrl"). -include_lib("jlib.hrl"). -include_lib("exml/include/exml.hrl"). @@ -108,7 +103,7 @@ -type row_batch() :: {TotalCount :: non_neg_integer(), Offset :: non_neg_integer(), MessageRows :: [row()]}. --type row() :: {mod_mam:message_id(), jid:jid(), exml:element()}. +-type row() :: mod_mam:message_row(). -export_type([row/0, row_batch/0]). @@ -382,7 +377,7 @@ handle_set_message_form(#jid{} = From, #jid{} = ArcJID, IQ) -> Params0 = mam_iq:form_to_lookup_params(IQ, mod_mam_params:max_result_limit(?MODULE, Host), mod_mam_params:default_result_limit(?MODULE, Host), mod_mam_params:extra_params_module(?MODULE, Host)), - Params = mam_iq:lookup_params_with_archive_details(Params0, ArcID, ArcJID), + Params = mam_iq:lookup_params_with_archive_details(Params0, ArcID, ArcJID, From), Result = lookup_messages(Host, Params), handle_lookup_result(Result, From, IQ, Params). @@ -422,11 +417,17 @@ forward_messages(From, ArcJID, MamNs, QueryID, MessageRows, SetClientNs) -> message_row_to_ext_id(lists:last(MessageRows)), is_user_identity_hidden(From, ArcJID)} end, - [send_message(ArcJID, From, message_row_to_xml(MamNs, From, HideUser, SetClientNs, Row, - QueryID)) + {ok, Host} = mongoose_subhosts:get_host(ArcJID#jid.lserver), + SendModule = mod_mam_params:send_message_mod(?MODULE, Host), + [send_message(SendModule, Row, ArcJID, From, + message_row_to_xml(MamNs, From, HideUser, SetClientNs, Row, + QueryID)) || Row <- MessageRows], {FirstMessID, LastMessID}. +send_message(SendModule, Row, ArcJID, From, Packet) -> + mam_send_message:call_send_message(SendModule, Row, ArcJID, From, Packet). + -spec handle_get_message_form(jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq(). handle_get_message_form(_From = #jid{lserver = Host}, _ArcJID = #jid{}, IQ = #iq{}) -> @@ -505,8 +506,8 @@ archive_message(Host, Params) -> -spec message_row_to_xml(binary(), jid:jid(), boolean(), boolean(), row(), binary() | undefined) -> exml:element(). -message_row_to_xml(MamNs, ReceiverJID, HideUser, SetClientNs, {MessID, SrcJID, Packet}, QueryID) -> - +message_row_to_xml(MamNs, ReceiverJID, HideUser, SetClientNs, + #{id := MessID, jid := SrcJID, packet := Packet}, QueryID) -> {Microseconds, _NodeMessID} = decode_compact_uuid(MessID), TS = calendar:system_time_to_rfc3339(erlang:convert_time_unit(Microseconds, microsecond, second), [{offset, "Z"}]), BExtMessID = mess_id_to_external_binary(MessID), @@ -536,7 +537,7 @@ replace_from_to_attributes(SrcJID, Packet = #xmlel{attrs = Attrs}) -> Packet#xmlel{attrs = NewAttrs}. -spec message_row_to_ext_id(row()) -> binary(). -message_row_to_ext_id({MessID, _, _}) -> +message_row_to_ext_id(#{id := MessID}) -> mess_id_to_external_binary(MessID). -spec handle_error_iq(mongoose_acc:t(), jid:lserver(), jid:jid(), atom(), diff --git a/src/mam/mod_mam_muc_cassandra_arch.erl b/src/mam/mod_mam_muc_cassandra_arch.erl index ea23104071..b279818df7 100644 --- a/src/mam/mod_mam_muc_cassandra_arch.erl +++ b/src/mam/mod_mam_muc_cassandra_arch.erl @@ -433,7 +433,7 @@ rows_to_uniform_format(MessageRows, RoomJID) -> row_to_uniform_format(#{nick_name := BNick, message := Data, id := MessID}, RoomJID) -> SrcJID = jid:replace_resource(RoomJID, BNick), Packet = stored_binary_to_packet(Data), - {MessID, SrcJID, Packet}. + #{id => MessID, jid => SrcJID, packet => Packet}. row_to_message_id(#{id := MsgID}) -> MsgID. diff --git a/src/mam/mod_mam_muc_elasticsearch_arch.erl b/src/mam/mod_mam_muc_elasticsearch_arch.erl index cc4a628111..d10ae965e2 100644 --- a/src/mam/mod_mam_muc_elasticsearch_arch.erl +++ b/src/mam/mod_mam_muc_elasticsearch_arch.erl @@ -276,14 +276,13 @@ search_result_to_mam_lookup_result(Result, Params) -> {CorrectedTotalCount, Offset, Messages} end. --spec hit_to_mam_message(map()) -> {mod_mam:message_id(), jid:jid(), exml:element()}. +-spec hit_to_mam_message(map()) -> mod_mam:message_row(). hit_to_mam_message(#{<<"_source">> := JSON}) -> MessageId = maps:get(<<"mam_id">>, JSON), Packet = maps:get(<<"message">>, JSON), SourceJid = maps:get(<<"source_jid">>, JSON), - {ok, Stanza} = exml:parse(Packet), - {MessageId, jid:from_binary(SourceJid), Stanza}. + #{id => MessageId, jid => jid:from_binary(SourceJid), packet => Stanza}. hit_to_gdpr_mam_message(#{<<"_source">> := JSON}) -> MessageId = maps:get(<<"mam_id">>, JSON), diff --git a/src/mam/mod_mam_params.erl b/src/mam/mod_mam_params.erl index 0fe986742a..c31ad34edd 100644 --- a/src/mam/mod_mam_params.erl +++ b/src/mam/mod_mam_params.erl @@ -19,8 +19,8 @@ -type mam_module() :: mod_mam | mod_mam_muc. -export([extra_params_module/2, max_result_limit/2, default_result_limit/2, - has_full_text_search/2, is_archivable_message_fun/2, archive_chat_markers/2, - add_stanzaid_element/2]). + has_full_text_search/2, is_archivable_message_fun/2, send_message_mod/2, + archive_chat_markers/2, add_stanzaid_element/2]). %%-------------------------------------------------------------------- %% API @@ -58,6 +58,10 @@ is_archivable_message_fun(Module, Host) -> end, {IsArchivableModule, IsArchivableFunction}. +-spec send_message_mod(mam_module(), Host :: jid:lserver()) -> module(). +send_message_mod(Module, Host) -> + param(Module, Host, send_message, mod_mam_utils). + -spec archive_chat_markers(mam_module(), Host :: jid:lserver()) -> boolean(). archive_chat_markers(Module, Host) -> param(Module, Host, archive_chat_markers, false). diff --git a/src/mam/mod_mam_rdbms_arch.erl b/src/mam/mod_mam_rdbms_arch.erl index 9f847244b2..72b0e05f13 100644 --- a/src/mam/mod_mam_rdbms_arch.erl +++ b/src/mam/mod_mam_rdbms_arch.erl @@ -86,7 +86,8 @@ get_mam_pm_gdpr_data(Acc, #jid{luser = User, lserver = Host} = ArcJID) -> [uniform_to_gdpr(row_to_uniform_format(Row, Env)) || Row <- Rows] ++ Acc end. -uniform_to_gdpr({MessID, RemoteJID, Packet}) -> +-spec uniform_to_gdpr(mod_mam:message_row()) -> tuple(). +uniform_to_gdpr(#{id := MessID, jid := RemoteJID, packet := Packet}) -> {integer_to_binary(MessID), jid:to_binary(RemoteJID), exml:to_binary(Packet)}. %% ---------------------------------------------------------------------- diff --git a/src/mam/mod_mam_riak_timed_arch_yz.erl b/src/mam/mod_mam_riak_timed_arch_yz.erl index c9b7f2312d..d8c1a3f82a 100644 --- a/src/mam/mod_mam_riak_timed_arch_yz.erl +++ b/src/mam/mod_mam_riak_timed_arch_yz.erl @@ -370,7 +370,7 @@ get_message2(Host, MsgId, Bucket, Key) -> SourceJID = riakc_map:fetch({<<"source_jid">>, register}, RiakMap), PacketBin = riakc_map:fetch({<<"packet">>, register}, RiakMap), Packet = stored_binary_to_packet(Host, PacketBin), - {MsgId, jid:from_binary(SourceJID), Packet}; + #{id => MsgId, jid => jid:from_binary(SourceJID), packet => Packet}; _ -> [] end. @@ -378,13 +378,13 @@ get_message2(Host, MsgId, Bucket, Key) -> ejabberd_gen_mam_archive:mam_pm_gdpr_data(). get_mam_pm_gdpr_data(Acc, OwnerJid) -> Messages = get_mam_gdpr_data(OwnerJid, <<"pm">>), - [{Id, jid:to_binary(Jid), exml:to_binary(Packet)} || {Id, Jid, Packet} <- Messages] ++ Acc. + [{Id, jid:to_binary(Jid), exml:to_binary(Packet)} || #{id := Id, jid := Jid, packet := Packet} <- Messages] ++ Acc. -spec get_mam_muc_gdpr_data(ejabberd_gen_mam_archive:mam_muc_gdpr_data(), jid:jid()) -> ejabberd_gen_mam_archive:mam_muc_gdpr_data(). get_mam_muc_gdpr_data(Acc, JID) -> Messages = get_mam_gdpr_data(JID, <<"muc">>), - [{MsgId, exml:to_binary(Packet)} || {MsgId, _, Packet} <- Messages] ++ Acc. + [{MsgId, exml:to_binary(Packet)} || #{id := MsgId, packet := Packet} <- Messages] ++ Acc. get_mam_gdpr_data(#jid{ lserver = LServer } = BareJid, Type) -> BareLJidBin = jid:to_binary(jid:to_lower(BareJid)), diff --git a/src/mam/mod_mam_utils.erl b/src/mam/mod_mam_utils.erl index e69715e9fa..cb10f6956e 100644 --- a/src/mam/mod_mam_utils.erl +++ b/src/mam/mod_mam_utils.erl @@ -85,7 +85,7 @@ check_for_item_not_found/3]). %% Ejabberd --export([send_message/3, +-export([send_message/4, maybe_set_client_xmlns/2, is_jid_in_user_roster/2]). @@ -1061,23 +1061,10 @@ wait_shaper(Host, Action, From) -> %% ----------------------------------------------------------------------- %% Ejabberd --spec send_message(jid:jid(), jid:jid(), exml:element() - ) -> mongoose_acc:t(). - --ifdef(MAM_COMPACT_FORWARDED). - -send_message(_From, To, Mess) -> - From = jid:from_binary(exml_query:attr(Mess, <<"from">>)), - ejabberd_sm:route(From, To, Mess). - --else. - -send_message(From, To, Mess) -> +-spec send_message(mod_mam:message_row(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t(). +send_message(_Row, From, To, Mess) -> ejabberd_sm:route(From, To, Mess). --endif. - - -spec is_jid_in_user_roster(jid:jid(), jid:jid()) -> boolean(). is_jid_in_user_roster(#jid{lserver = LServer} = ToJID, #jid{} = RemJID) -> @@ -1129,7 +1116,7 @@ is_policy_violation(TotalCount, Offset, MaxResultLimit, LimitPassed) -> check_for_item_not_found(#rsm_in{direction = before, id = ID}, PageSize, {TotalCount, Offset, MessageRows}) -> case maybe_last(MessageRows) of - {ok, {ID, _, _}} = _IntervalEndpoint -> + {ok, #{id := ID}} = _IntervalEndpoint -> Page = lists:sublist(MessageRows, PageSize), {ok, {TotalCount, Offset, Page}}; undefined -> @@ -1138,7 +1125,7 @@ check_for_item_not_found(#rsm_in{direction = before, id = ID}, check_for_item_not_found(#rsm_in{direction = aft, id = ID}, _PageSize, {TotalCount, Offset, MessageRows0}) -> case MessageRows0 of - [{ID, _, _} = _IntervalEndpoint | MessageRows] -> + [#{id := ID} = _IntervalEndpoint | MessageRows] -> {ok, {TotalCount, Offset, MessageRows}}; _ -> {error, item_not_found} diff --git a/src/mod_commands.erl b/src/mod_commands.erl index 9d407c2285..1a91f1467f 100644 --- a/src/mod_commands.erl +++ b/src/mod_commands.erl @@ -413,7 +413,7 @@ get_recent_messages(Caller, With, 0, Limit) -> get_recent_messages(Caller, With, Future, Limit); get_recent_messages(Caller, With, Before, Limit) -> Res = lookup_recent_messages(Caller, With, Before, Limit), - lists:map(fun record_to_map/1, Res). + lists:map(fun row_to_map/1, Res). change_user_password(Host, User, Password) -> JID = jid:make(User, Host, <<>>), @@ -427,7 +427,8 @@ change_user_password(Host, User, Password) -> {error, bad_request, "invalid jid"} end. -record_to_map({Id, From, Msg}) -> +-spec row_to_map(mod_mam:message_row()) -> map(). +row_to_map(#{id := Id, jid := From, packet := Msg}) -> Jbin = jid:to_binary(From), {Msec, _} = mod_mam_utils:decode_compact_uuid(Id), MsgId = case xml:get_tag_attr(<<"id">>, Msg) of diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 355801e630..a94625adfd 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -4319,18 +4319,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(). diff --git a/src/mongoose_client_api/mongoose_client_api_messages.erl b/src/mongoose_client_api/mongoose_client_api_messages.erl index a159c1cc33..5788f1cb80 100644 --- a/src/mongoose_client_api/mongoose_client_api_messages.erl +++ b/src/mongoose_client_api/mongoose_client_api_messages.erl @@ -75,7 +75,7 @@ maybe_to_json_with_jid(WithJID, #jid{lserver = Server} = JID, Req, State) -> max_result_limit => 50, is_simple => true}), {ok, {_, _, Msgs}} = R, - Resp = [make_json_msg(Msg, MAMId) || {MAMId, _, Msg} <- Msgs], + Resp = [make_json_msg(Msg, MAMId) || #{id := MAMId, packet := Msg} <- Msgs], {jiffy:encode(Resp), Req, State}. send_message(Req, #{user := RawUser, jid := FromJID} = State) -> diff --git a/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl b/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl index f15e7d33dd..7a64ebbdca 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl @@ -61,6 +61,7 @@ to_json(Req, #{jid := UserJID, room := Room} = State) -> R = mod_mam_muc:lookup_messages(Server, #{archive_id => ArchiveID, owner_jid => RoomJID, + caller_jid => UserJID, rsm => RSM, borders => undefined, start_ts => undefined, @@ -159,7 +160,8 @@ encode(Packet, Timestamp) -> Msg = make_json_item(Packet, FromJID, Timestamp), Msg#{room => FromJID#jid.luser}. -make_json_item({MAMID, JID, Msg}) -> +-spec make_json_item(mod_mam:message_row()) -> term(). +make_json_item(#{id := MAMID, jid := JID, packet := Msg}) -> {Microsec, _} = mod_mam_utils:decode_compact_uuid(MAMID), make_json_item(Msg, JID, Microsec div 1000). diff --git a/src/xml.erl b/src/xml.erl index c5c1c8444c..3ab7c3d036 100644 --- a/src/xml.erl +++ b/src/xml.erl @@ -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"). @@ -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) ->