1 |
|
-module(mongoose_stanza_api). |
2 |
|
|
3 |
|
%% API |
4 |
|
-export([send_chat_message/4, send_headline_message/5, send_stanza/2, lookup_recent_messages/5]). |
5 |
|
|
6 |
|
%% Event API |
7 |
|
-export([open_session/2, close_session/1]). |
8 |
|
|
9 |
|
-include("jlib.hrl"). |
10 |
|
-include("mongoose_rsm.hrl"). |
11 |
|
-include("mongoose_logger.hrl"). |
12 |
|
|
13 |
|
-type stream() :: #{jid := jid:jid(), |
14 |
|
sid := ejabberd_sm:sid(), |
15 |
|
host_type := mongooseim:host_type()}. |
16 |
|
|
17 |
|
%% API |
18 |
|
|
19 |
|
-spec send_chat_message(User :: jid:jid() | undefined, From :: jid:jid() | undefined, |
20 |
|
To :: jid:jid(), Body :: binary()) -> |
21 |
|
{unknown_user | invalid_sender, iodata()} | {ok, map()}. |
22 |
|
send_chat_message(User, From, To, Body) -> |
23 |
51 |
M = #{user => User, from => From, to => To, body => Body}, |
24 |
51 |
fold(M, [fun check_sender/1, fun get_host_type/1, fun check_user/1, |
25 |
|
fun prepare_chat_message/1, fun send/1]). |
26 |
|
|
27 |
|
-spec send_headline_message(User :: jid:jid() | undefined, From :: jid:jid() | undefined, |
28 |
|
To :: jid:jid(), Body :: binary() | undefined, |
29 |
|
Subject :: binary() | undefined) -> |
30 |
|
{unknown_user | invalid_sender, iodata()} | {ok, map()}. |
31 |
|
send_headline_message(User, From, To, Body, Subject) -> |
32 |
8 |
M = #{user => User, from => From, to => To, body => Body, subject => Subject}, |
33 |
8 |
fold(M, [fun check_sender/1, fun get_host_type/1, fun check_user/1, |
34 |
|
fun prepare_headline_message/1, fun send/1]). |
35 |
|
|
36 |
|
-spec send_stanza(User :: jid:jid() | undefined, exml:element()) -> |
37 |
|
{unknown_user | invalid_sender | no_sender | |
38 |
|
invalid_recipient | no_recipient, iodata()} | {ok, map()}. |
39 |
|
send_stanza(User, Stanza) -> |
40 |
16 |
M = #{user => User, stanza => Stanza}, |
41 |
16 |
fold(M, [fun extract_from_jid/1, fun extract_to_jid/1, fun check_sender/1, |
42 |
|
fun get_host_type/1, fun check_user/1, fun prepare_stanza/1, fun send/1]). |
43 |
|
|
44 |
|
-spec lookup_recent_messages(User :: jid:jid(), With :: jid:jid() | undefined, |
45 |
|
Before :: mod_mam:unix_timestamp() | undefined, % microseconds |
46 |
|
Limit :: non_neg_integer() | undefined, CheckUser :: boolean()) -> |
47 |
|
{unknown_user, iodata()} | {ok, {[mod_mam:message_row()], non_neg_integer()}}. |
48 |
|
lookup_recent_messages(User, With, Before, Limit, CheckUser) -> |
49 |
48 |
M = #{user => User, with => With, before => Before, limit => Limit, check_user => CheckUser}, |
50 |
48 |
fold(M, [fun get_host_type/1, fun check_user/1, fun lookup_messages/1]). |
51 |
|
|
52 |
|
%% Event API |
53 |
|
|
54 |
|
-spec open_session(jid:jid(), boolean()) -> {unknown_user, iodata()} | {ok, stream()}. |
55 |
|
open_session(User, CheckUser) -> |
56 |
8 |
M = #{user => User, check_user => CheckUser}, |
57 |
8 |
fold(M, [fun get_host_type/1, fun check_user/1, fun do_open_session/1]). |
58 |
|
|
59 |
|
-spec close_session(stream()) -> {ok, closed}. |
60 |
|
close_session(#{jid := Jid = #jid{lserver = S}, sid := Sid, host_type := HostType}) -> |
61 |
3 |
Acc = mongoose_acc:new( |
62 |
|
#{location => ?LOCATION, |
63 |
|
lserver => S, |
64 |
|
host_type => HostType, |
65 |
|
element => undefined}), |
66 |
3 |
ejabberd_sm:close_session(Acc, Sid, Jid, normal, #{}), |
67 |
3 |
{ok, closed}. |
68 |
|
|
69 |
|
%% Steps |
70 |
|
|
71 |
|
%% @doc Determine the user's bare JID and the 'from' JID, and check if they match |
72 |
|
check_sender(M = #{user := User = #jid{}, from := From = #jid{}}) -> |
73 |
7 |
case jid:are_bare_equal(User, From) of |
74 |
|
true -> |
75 |
4 |
M#{check_user => false}; |
76 |
|
false -> |
77 |
3 |
{invalid_sender, <<"Sender's JID is different from the user's JID">>} |
78 |
|
end; |
79 |
|
check_sender(M = #{from := From = #jid{}}) -> |
80 |
54 |
M#{user => jid:to_bare(From), check_user => true}; |
81 |
|
check_sender(M = #{user := User = #jid{}}) -> |
82 |
10 |
M#{from => User, check_user => false}; |
83 |
|
check_sender(#{}) -> |
84 |
1 |
{no_sender, <<"Missing sender JID">>}. |
85 |
|
|
86 |
|
extract_from_jid(M = #{stanza := Stanza}) -> |
87 |
16 |
case exml_query:attr(Stanza, <<"from">>) of |
88 |
|
undefined -> |
89 |
2 |
M; |
90 |
|
JidBin -> |
91 |
14 |
case jid:from_binary(JidBin) of |
92 |
1 |
error -> {invalid_sender, <<"Invalid sender JID">>}; |
93 |
13 |
Jid -> M#{from => Jid} |
94 |
|
end |
95 |
|
end. |
96 |
|
|
97 |
|
extract_to_jid(M = #{stanza := Stanza}) -> |
98 |
15 |
case exml_query:attr(Stanza, <<"to">>) of |
99 |
|
undefined -> |
100 |
1 |
{no_recipient, <<"Missing recipient JID">>}; |
101 |
|
JidBin -> |
102 |
14 |
case jid:from_binary(JidBin) of |
103 |
1 |
error -> {invalid_recipient, <<"Invalid recipient JID">>}; |
104 |
13 |
Jid -> M#{to => Jid} |
105 |
|
end |
106 |
|
end. |
107 |
|
|
108 |
|
prepare_chat_message(M = #{from := From, to := To, body := Body}) -> |
109 |
48 |
FromBin = jid:to_binary(From), |
110 |
48 |
ToBin = jid:to_binary(To), |
111 |
48 |
M#{stanza => build_chat_message(FromBin, ToBin, Body)}. |
112 |
|
|
113 |
|
prepare_headline_message(M = #{from := From, to := To, body := Body, subject := Subject}) -> |
114 |
7 |
FromBin = jid:to_binary(From), |
115 |
7 |
ToBin = jid:to_binary(To), |
116 |
7 |
M#{stanza => build_headline_message(FromBin, ToBin, Body, Subject)}. |
117 |
|
|
118 |
|
prepare_stanza(M = #{stanza := Stanza}) -> |
119 |
5 |
M#{stanza := ensure_id(Stanza)}. |
120 |
|
|
121 |
|
get_host_type(M = #{user := #jid{lserver = LServer}}) -> |
122 |
124 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
123 |
|
{ok, HostType} -> |
124 |
120 |
M#{host_type => HostType}; |
125 |
|
{error, not_found} -> |
126 |
4 |
{unknown_user, <<"User's domain does not exist">>} |
127 |
|
end. |
128 |
|
|
129 |
|
check_user(M = #{check_user := false}) -> |
130 |
33 |
M; |
131 |
|
check_user(M = #{check_user := true, user := UserJid, host_type := HostType}) -> |
132 |
87 |
case ejabberd_auth:does_user_exist(HostType, UserJid, stored) of |
133 |
|
true -> |
134 |
78 |
M; |
135 |
|
false -> |
136 |
9 |
{unknown_user, <<"User does not exist">>} |
137 |
|
end. |
138 |
|
|
139 |
|
send(#{host_type := HostType, from := From, to := To, stanza := Stanza}) -> |
140 |
60 |
Acc = mongoose_acc:new(#{from_jid => From, |
141 |
|
to_jid => To, |
142 |
|
location => ?LOCATION, |
143 |
|
host_type => HostType, |
144 |
|
lserver => From#jid.lserver, |
145 |
|
element => Stanza}), |
146 |
60 |
C2SData = mongoose_c2s:create_data(#{host_type => HostType, jid => From}), |
147 |
60 |
Params = mongoose_c2s:hook_arg(C2SData, session_established, internal, Stanza, undefined), |
148 |
60 |
case mongoose_c2s_hooks:user_send_packet(HostType, Acc, Params) of |
149 |
|
{ok, Acc1} -> |
150 |
60 |
{_, Acc2} = handle_message(HostType, Acc1, Params), |
151 |
60 |
ejabberd_router:route(From, To, Acc2); |
152 |
|
{stop, Acc1} -> |
153 |
:-( |
Acc1 |
154 |
|
end, |
155 |
60 |
{ok, #{<<"id">> => get_id(Stanza)}}. |
156 |
|
|
157 |
|
lookup_messages(#{user := UserJid, with := WithJid, before := Before, limit := Limit, |
158 |
|
host_type := HostType}) -> |
159 |
44 |
#jid{luser = LUser, lserver = LServer} = UserJid, |
160 |
44 |
MaxResultLimit = mod_mam_params:max_result_limit(mod_mam_pm, HostType), |
161 |
44 |
Limit2 = min(Limit, MaxResultLimit), |
162 |
44 |
Params = #{archive_id => mod_mam_pm:archive_id(LServer, LUser), |
163 |
|
owner_jid => UserJid, |
164 |
|
borders => undefined, |
165 |
|
rsm => #rsm_in{direction = before, id = undefined}, % last msgs |
166 |
|
start_ts => undefined, |
167 |
|
end_ts => Before, |
168 |
|
now => os:system_time(microsecond), |
169 |
|
with_jid => WithJid, |
170 |
|
search_text => undefined, |
171 |
|
page_size => Limit2, |
172 |
|
limit_passed => false, |
173 |
|
max_result_limit => MaxResultLimit, |
174 |
|
is_simple => true}, |
175 |
44 |
{ok, {_, _, Rows}} = mod_mam_pm:lookup_messages(HostType, Params), |
176 |
44 |
{ok, {Rows, Limit2}}. |
177 |
|
|
178 |
|
do_open_session(#{host_type := HostType, user := JID}) -> |
179 |
7 |
SID = ejabberd_sm:make_new_sid(), |
180 |
7 |
UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), |
181 |
7 |
Resource = <<"sse-", UUID/binary>>, |
182 |
7 |
NewJid = jid:replace_resource(JID, Resource), |
183 |
7 |
ejabberd_sm:open_session(HostType, SID, NewJid, 1, #{}), |
184 |
7 |
{ok, #{sid => SID, jid => NewJid, host_type => HostType}}. |
185 |
|
|
186 |
|
%% Helpers |
187 |
|
|
188 |
|
-spec build_chat_message(jid:literal_jid(), jid:literal_jid(), binary()) -> exml:element(). |
189 |
|
build_chat_message(From, To, Body) -> |
190 |
48 |
#xmlel{name = <<"message">>, |
191 |
|
attrs = add_id([{<<"type">>, <<"chat">>}, {<<"from">>, From}, {<<"to">>, To}]), |
192 |
|
children = [#xmlel{name = <<"body">>, |
193 |
|
children = [#xmlcdata{content = Body}]}] |
194 |
|
}. |
195 |
|
|
196 |
|
-spec build_headline_message(jid:literal_jid(), jid:literal_jid(), |
197 |
|
binary() | undefined, binary() | undefined) -> exml:element(). |
198 |
|
build_headline_message(From, To, Body, Subject) -> |
199 |
7 |
Children = maybe_cdata_elem(<<"subject">>, Subject) ++ |
200 |
|
maybe_cdata_elem(<<"body">>, Body), |
201 |
7 |
Attrs = add_id([{<<"type">>, <<"headline">>}, {<<"from">>, From}, {<<"to">>, To}]), |
202 |
7 |
#xmlel{name = <<"message">>, attrs = Attrs, children = Children}. |
203 |
|
|
204 |
|
-spec ensure_id(exml:element()) -> exml:element(). |
205 |
|
ensure_id(Stanza) -> |
206 |
5 |
case get_id(Stanza) of |
207 |
4 |
undefined -> Stanza#xmlel{attrs = add_id(Stanza#xmlel.attrs)}; |
208 |
1 |
_ -> Stanza |
209 |
|
end. |
210 |
|
|
211 |
6 |
maybe_cdata_elem(_, undefined) -> []; |
212 |
|
maybe_cdata_elem(Name, Text) when is_binary(Text) -> |
213 |
8 |
[cdata_elem(Name, Text)]. |
214 |
|
|
215 |
|
cdata_elem(Name, Text) when is_binary(Name), is_binary(Text) -> |
216 |
8 |
#xmlel{name = Name, children = [#xmlcdata{content = Text}]}. |
217 |
|
|
218 |
|
add_id(Attrs) -> |
219 |
59 |
[{<<"id">>, mongoose_bin:gen_from_crypto()} | Attrs]. |
220 |
|
|
221 |
|
-spec get_id(exml:element()) -> binary() | undefined. |
222 |
|
get_id(Stanza) -> |
223 |
65 |
exml_query:attr(Stanza, <<"id">>, undefined). |
224 |
|
|
225 |
|
fold({_, _} = Result, _) -> |
226 |
131 |
Result; |
227 |
|
fold(M, [Step | Rest]) when is_map(M) -> |
228 |
518 |
fold(Step(M), Rest). |
229 |
|
|
230 |
|
handle_message(HostType, Acc, Params) -> |
231 |
60 |
case mongoose_acc:stanza_name(Acc) of |
232 |
|
<<"message">> -> |
233 |
60 |
mongoose_c2s_hooks:user_send_message(HostType, Acc, Params); |
234 |
|
_ -> |
235 |
:-( |
{ok, Acc} |
236 |
|
end. |