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