1 |
|
-module(mod_inbox_entries). |
2 |
|
|
3 |
|
-include("mongoose_ns.hrl"). |
4 |
|
-include("jlib.hrl"). |
5 |
|
-include("mod_inbox.hrl"). |
6 |
|
|
7 |
|
% Inbox extensions |
8 |
|
-export([process_iq_conversation/5]). |
9 |
|
-export([should_be_stored_in_inbox/1]). |
10 |
|
|
11 |
|
-type entry_query() :: mod_inbox:entry_properties(). |
12 |
|
|
13 |
|
-type get_entry_type() :: full_entry | only_properties. |
14 |
|
-export_type([get_entry_type/0]). |
15 |
|
|
16 |
|
-spec process_iq_conversation(Acc :: mongoose_acc:t(), |
17 |
|
From :: jid:jid(), |
18 |
|
To :: jid:jid(), |
19 |
|
IQ :: jlib:iq(), |
20 |
|
Extra :: map()) -> |
21 |
|
{mongoose_acc:t(), jlib:iq()}. |
22 |
|
process_iq_conversation(Acc, From, _To, #iq{type = get, sub_el = SubEl} = IQ, _Extra) -> |
23 |
6 |
process_iq_conversation_get(Acc, IQ, From, SubEl); |
24 |
|
process_iq_conversation(Acc, From, _To, #iq{type = set, |
25 |
|
sub_el = #xmlel{name = <<"reset">>} = ResetStanza} = IQ, |
26 |
|
_Extra) -> |
27 |
10 |
maybe_process_reset_stanza(Acc, From, IQ, ResetStanza); |
28 |
|
process_iq_conversation(Acc, From, _To, #iq{type = set, |
29 |
|
sub_el = #xmlel{name = <<"query">>} = Query} = IQ, |
30 |
|
_Extra) -> |
31 |
118 |
process_iq_conversation_set(Acc, IQ, From, Query). |
32 |
|
|
33 |
|
-spec process_iq_conversation_get(mongoose_acc:t(), jlib:iq(), jid:jid(), exml:element()) -> |
34 |
|
{mongoose_acc:t(), jlib:iq()}. |
35 |
|
process_iq_conversation_get(Acc, IQ, From, SubEl) -> |
36 |
6 |
case mod_inbox_utils:extract_attr_jid(SubEl) of |
37 |
|
{error, _} -> |
38 |
2 |
HostType = mongoose_acc:host_type(Acc), |
39 |
2 |
Form = build_inbox_entry_form(HostType), |
40 |
2 |
SubElWithForm = SubEl#xmlel{children = [Form]}, |
41 |
2 |
{Acc, IQ#iq{type = result, sub_el = SubElWithForm}}; |
42 |
|
EntryJID -> |
43 |
4 |
FullEntry = maybe_get_full_entry(SubEl), |
44 |
4 |
get_properties_for_jid(Acc, IQ, From, EntryJID, FullEntry) |
45 |
|
end. |
46 |
|
|
47 |
|
-spec maybe_get_full_entry(exml:element()) -> get_entry_type(). |
48 |
|
maybe_get_full_entry(SubEl) -> |
49 |
4 |
case exml_query:attr(SubEl, <<"complete">>) of |
50 |
2 |
<<"true">> -> full_entry; |
51 |
2 |
_ -> only_properties |
52 |
|
end. |
53 |
|
|
54 |
|
-spec build_inbox_entry_form(mongooseim:host_type()) -> exml:element(). |
55 |
|
build_inbox_entry_form(HostType) -> |
56 |
2 |
AllBoxes = mod_inbox_utils:all_valid_boxes_for_query(HostType), |
57 |
2 |
Fields = |
58 |
|
[#{var => <<"box">>, type => <<"list-single">>, values => [<<"all">>], options => AllBoxes}, |
59 |
|
#{var => <<"archive">>, type => <<"boolean">>, values => [<<"false">>]}, |
60 |
|
#{var => <<"read">>, type => <<"boolean">>, values => [<<"false">>]}, |
61 |
|
#{var => <<"mute">>, type => <<"text-single">>, values => [<<"0">>]}], |
62 |
2 |
mongoose_data_forms:form(#{ns => ?NS_ESL_INBOX_CONVERSATION, fields => Fields}). |
63 |
|
|
64 |
|
fetch_right_query(HostType, InboxEntryKey, only_properties) -> |
65 |
2 |
mod_inbox_backend:get_entry_properties(HostType, InboxEntryKey); |
66 |
|
fetch_right_query(HostType, InboxEntryKey, full_entry) -> |
67 |
2 |
mod_inbox_backend:get_full_entry(HostType, InboxEntryKey). |
68 |
|
|
69 |
|
-spec get_properties_for_jid(mongoose_acc:t(), jlib:iq(), jid:jid(), jid:jid(), get_entry_type()) -> |
70 |
|
{mongoose_acc:t(), jlib:iq()}. |
71 |
|
get_properties_for_jid(Acc, IQ, From, EntryJID, QueryType) -> |
72 |
4 |
HostType = mongoose_acc:host_type(Acc), |
73 |
4 |
{_, _, BinEntryJID} = InboxEntryKey = mod_inbox_utils:build_inbox_entry_key(From, EntryJID), |
74 |
4 |
case fetch_right_query(HostType, InboxEntryKey, QueryType) of |
75 |
:-( |
[] -> return_error(Acc, IQ, item_not_found, <<"Entry not found">>); |
76 |
|
Result -> |
77 |
4 |
Children = build_query_children(Acc, Result, IQ, QueryType), |
78 |
4 |
X = [#xmlel{name = <<"query">>, |
79 |
|
attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION}, |
80 |
|
{<<"jid">>, BinEntryJID}], |
81 |
|
children = Children}], |
82 |
4 |
{Acc, IQ#iq{type = result, sub_el = X}} |
83 |
|
end. |
84 |
|
|
85 |
|
-spec process_iq_conversation_set(mongoose_acc:t(), jlib:iq(), jid:jid(), exml:element()) -> |
86 |
|
{mongoose_acc:t(), jlib:iq()}. |
87 |
|
process_iq_conversation_set(Acc, IQ, From, #xmlel{children = Requests} = Query) -> |
88 |
118 |
case mod_inbox_utils:extract_attr_jid(Query) of |
89 |
|
{error, Msg} -> |
90 |
4 |
return_error(Acc, IQ, bad_request, Msg); |
91 |
|
EntryJID -> |
92 |
114 |
extract_requests(Acc, IQ, From, EntryJID, Requests) |
93 |
|
end. |
94 |
|
|
95 |
|
-spec extract_requests(mongoose_acc:t(), jlib:iq(), jid:jid(), jid:jid(), [exml:element()]) -> |
96 |
|
{mongoose_acc:t(), jlib:iq()}. |
97 |
|
extract_requests(Acc, IQ, From, EntryJID, Requests) -> |
98 |
114 |
case form_to_query(Acc, Requests, #{}) of |
99 |
|
{error, Msg} -> |
100 |
12 |
return_error(Acc, IQ, bad_request, Msg); |
101 |
|
Params -> |
102 |
102 |
process_requests(Acc, IQ, From, EntryJID, Params) |
103 |
|
end. |
104 |
|
|
105 |
|
-spec process_requests(mongoose_acc:t(), jlib:iq(), jid:jid(), jid:jid(), entry_query()) -> |
106 |
|
{mongoose_acc:t(), jlib:iq()}. |
107 |
|
process_requests(Acc, IQ, From, EntryJID, Params) -> |
108 |
102 |
HostType = mongoose_acc:host_type(Acc), |
109 |
102 |
InboxEntryKey = mod_inbox_utils:build_inbox_entry_key(From, EntryJID), |
110 |
102 |
case mod_inbox_backend:set_entry_properties(HostType, InboxEntryKey, Params) of |
111 |
|
{error, Msg} -> |
112 |
8 |
return_error(Acc, IQ, bad_request, Msg); |
113 |
|
Result -> |
114 |
94 |
forward_result(Acc, IQ, From, InboxEntryKey, Result) |
115 |
|
end. |
116 |
|
|
117 |
|
-spec forward_result(mongoose_acc:t(), jlib:iq(), jid:jid(), mod_inbox:entry_key(), entry_properties()) -> |
118 |
|
{mongoose_acc:t(), jlib:iq()}. |
119 |
|
forward_result(Acc, IQ = #iq{id = IqId}, From, {_, _, ToBareJidBin}, Result) -> |
120 |
94 |
Children0 = build_query_children(Acc, Result, IQ, only_properties), |
121 |
94 |
Children1 = iq_x_wrapper(IQ, ToBareJidBin, Children0), |
122 |
94 |
Msg = #xmlel{name = <<"message">>, attrs = [{<<"id">>, IqId}], children = Children1}, |
123 |
94 |
Acc1 = ejabberd_router:route(From, jid:to_bare(From), Acc, Msg), |
124 |
94 |
Res = IQ#iq{type = result, sub_el = []}, |
125 |
94 |
{Acc1, Res}. |
126 |
|
|
127 |
|
-spec iq_x_wrapper(jlib:iq(), binary(), [exml:element()]) -> [exml:element()]. |
128 |
|
iq_x_wrapper(#iq{id = IqId, sub_el = Query}, Jid, Properties) -> |
129 |
94 |
[#xmlel{name = <<"x">>, |
130 |
|
attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION}, |
131 |
|
{<<"jid">>, Jid}, |
132 |
|
{<<"queryid">>, exml_query:attr(Query, <<"queryid">>, IqId)}], |
133 |
|
children = Properties}]. |
134 |
|
|
135 |
|
maybe_process_reset_stanza(Acc, From, IQ, ResetStanza) -> |
136 |
10 |
case mod_inbox_utils:extract_attr_jid(ResetStanza) of |
137 |
|
{error, Msg} -> |
138 |
4 |
return_error(Acc, IQ, bad_request, Msg); |
139 |
|
InterlocutorJID -> |
140 |
6 |
process_reset_stanza(Acc, From, IQ, ResetStanza, InterlocutorJID) |
141 |
|
end. |
142 |
|
|
143 |
|
process_reset_stanza(Acc, From, IQ, _ResetStanza, InterlocutorJID) -> |
144 |
6 |
ok = mod_inbox_utils:reset_unread_count_to_zero(Acc, From, InterlocutorJID), |
145 |
6 |
Res = IQ#iq{type = result, |
146 |
|
sub_el = [#xmlel{name = <<"reset">>, |
147 |
|
attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION}], |
148 |
|
children = []}]}, |
149 |
6 |
{Acc, Res}. |
150 |
|
|
151 |
|
%%-------------------------------------------------------------------- |
152 |
|
%% Helpers |
153 |
|
%%-------------------------------------------------------------------- |
154 |
|
|
155 |
|
-spec build_query_children(mongoose_acc:t(), inbox_res() | entry_properties(), jlib:iq(), get_entry_type()) -> |
156 |
|
[exml:element()]. |
157 |
|
build_query_children(Acc, Entry0, IQ, full_entry) -> |
158 |
2 |
CurrentTS = mongoose_acc:timestamp(Acc), |
159 |
2 |
[Entry1] = mongoose_hooks:extend_inbox_result(Acc, [Entry0], IQ), |
160 |
2 |
mod_inbox_utils:build_inbox_result_elements(Entry1, CurrentTS); |
161 |
|
build_query_children(Acc, Entry, _IQ, only_properties) -> |
162 |
96 |
CurrentTS = mongoose_acc:timestamp(Acc), |
163 |
96 |
mod_inbox_utils:build_entry_result_elements(Entry, CurrentTS). |
164 |
|
|
165 |
|
-spec return_error(mongoose_acc:t(), jlib:iq(), bad_request | item_not_found, binary()) -> |
166 |
|
{mongoose_acc:t(), jlib:iq()}. |
167 |
|
return_error(Acc, IQ, Type, Msg) when is_binary(Msg) -> |
168 |
28 |
{Acc, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:Type(<<"en">>, Msg)]}}. |
169 |
|
|
170 |
|
-spec form_to_query(mongoose_acc:t(), [exml:element()], map()) -> entry_query() | {error, binary()}. |
171 |
|
form_to_query(_, [], Acc) when map_size(Acc) == 0 -> |
172 |
2 |
{error, <<"no-property">>}; |
173 |
|
form_to_query(_, [], Acc) -> |
174 |
102 |
Acc; |
175 |
|
form_to_query(MAcc, [#xmlel{name = <<"box">>, children = [#xmlcdata{content = BoxName}]} | Rest], Acc) -> |
176 |
40 |
case is_box_accepted(MAcc, BoxName) of |
177 |
38 |
true -> form_to_query(MAcc, Rest, Acc#{box => BoxName}); |
178 |
2 |
false -> {error, <<"invalid-box">>} |
179 |
|
end; |
180 |
|
form_to_query(MAcc, [#xmlel{name = <<"archive">>, children = [#xmlcdata{content = <<"true">>}]} | Rest], Acc) -> |
181 |
28 |
form_to_query(MAcc, Rest, Acc#{box => maps:get(box, Acc, <<"archive">>)}); |
182 |
|
form_to_query(MAcc, [#xmlel{name = <<"archive">>, children = [#xmlcdata{content = <<"false">>}]} | Rest], Acc) -> |
183 |
2 |
form_to_query(MAcc, Rest, Acc#{box => maps:get(box, Acc, <<"inbox">>)}); |
184 |
|
form_to_query(MAcc, [#xmlel{name = <<"read">>, children = [#xmlcdata{content = <<"true">>}]} | Rest], Acc) -> |
185 |
16 |
form_to_query(MAcc, Rest, Acc#{unread_count => 0}); |
186 |
|
form_to_query(MAcc, [#xmlel{name = <<"read">>, children = [#xmlcdata{content = <<"false">>}]} | Rest], Acc) -> |
187 |
8 |
form_to_query(MAcc, Rest, Acc#{unread_count => 1}); |
188 |
|
form_to_query(MAcc, [#xmlel{name = <<"mute">>, |
189 |
|
children = [#xmlcdata{content = Value}]} | Rest], Acc) -> |
190 |
24 |
case mod_inbox_utils:maybe_binary_to_positive_integer(Value) of |
191 |
4 |
{error, _} -> {error, <<"bad-request">>}; |
192 |
|
0 -> |
193 |
2 |
form_to_query(MAcc, Rest, Acc#{muted_until => 0}); |
194 |
|
Num when Num > 0 -> |
195 |
18 |
TS = mongoose_acc:timestamp(MAcc), |
196 |
18 |
MutedUntilMicroSec = TS + erlang:convert_time_unit(Num, second, microsecond), |
197 |
18 |
form_to_query(MAcc, Rest, Acc#{muted_until => MutedUntilMicroSec}) |
198 |
|
end; |
199 |
|
form_to_query(_, _, _) -> |
200 |
4 |
{error, <<"bad-request">>}. |
201 |
|
|
202 |
|
is_box_accepted(MAcc, Box) -> |
203 |
40 |
HostType = mongoose_acc:host_type(MAcc), |
204 |
40 |
BoxOptions = gen_mod:get_module_opt(HostType, mod_inbox, boxes), |
205 |
40 |
lists:member(Box, BoxOptions). |
206 |
|
|
207 |
|
-spec should_be_stored_in_inbox(exml:element()) -> boolean(). |
208 |
|
should_be_stored_in_inbox(Msg) -> |
209 |
2194 |
not is_inbox_update(Msg). |
210 |
|
|
211 |
|
-spec is_inbox_update(exml:element()) -> boolean(). |
212 |
|
is_inbox_update(Msg) -> |
213 |
2194 |
case exml_query:subelement_with_ns(Msg, ?NS_ESL_INBOX_CONVERSATION, undefined) of |
214 |
2194 |
undefined -> false; |
215 |
:-( |
_ -> true |
216 |
|
end. |