1 |
|
-module(mongoose_client_api_rooms_messages). |
2 |
|
-behaviour(cowboy_rest). |
3 |
|
|
4 |
|
-export([trails/0]). |
5 |
|
|
6 |
|
-export([init/2]). |
7 |
|
-export([content_types_provided/2]). |
8 |
|
-export([content_types_accepted/2]). |
9 |
|
-export([is_authorized/2]). |
10 |
|
-export([allowed_methods/2]). |
11 |
|
-export([resource_exists/2]). |
12 |
|
-export([allow_missing_post/2]). |
13 |
|
|
14 |
|
-export([to_json/2]). |
15 |
|
-export([from_json/2]). |
16 |
|
-export([encode/2]). |
17 |
|
|
18 |
|
-ignore_xref([from_json/2, to_json/2, trails/0]). |
19 |
|
|
20 |
|
-import(mongoose_client_api_messages, [maybe_integer/1, maybe_before_to_us/2]). |
21 |
|
|
22 |
|
-include("mongoose.hrl"). |
23 |
|
-include("jlib.hrl"). |
24 |
|
-include("mongoose_rsm.hrl"). |
25 |
|
-include_lib("exml/include/exml.hrl"). |
26 |
|
|
27 |
|
trails() -> |
28 |
80 |
mongoose_client_api_rooms_messages_doc:trails(). |
29 |
|
|
30 |
|
init(Req, Opts) -> |
31 |
8 |
mongoose_client_api:init(Req, Opts). |
32 |
|
|
33 |
|
is_authorized(Req, State) -> |
34 |
8 |
mongoose_client_api:is_authorized(Req, State). |
35 |
|
|
36 |
|
content_types_provided(Req, State) -> |
37 |
8 |
mongoose_client_api_rooms:content_types_provided(Req, State). |
38 |
|
|
39 |
|
content_types_accepted(Req, State) -> |
40 |
8 |
mongoose_client_api_rooms:content_types_accepted(Req, State). |
41 |
|
|
42 |
|
allowed_methods(Req, State) -> |
43 |
8 |
{[<<"OPTIONS">>, <<"GET">>, <<"POST">>], Req, State}. |
44 |
|
|
45 |
|
resource_exists(Req, State) -> |
46 |
8 |
mongoose_client_api_rooms:resource_exists(Req, State). |
47 |
|
|
48 |
|
allow_missing_post(Req, State) -> |
49 |
:-( |
{false, Req, State}. |
50 |
|
|
51 |
|
to_json(Req, #{role_in_room := none} = State) -> |
52 |
:-( |
mongoose_client_api:forbidden_request(Req, State); |
53 |
|
to_json(Req, #{jid := UserJID, room := Room} = State) -> |
54 |
:-( |
RoomJID = maps:get(jid, Room), |
55 |
:-( |
HostType = mod_muc_light_utils:room_jid_to_host_type(RoomJID), |
56 |
:-( |
Now = os:system_time(microsecond), |
57 |
:-( |
ArchiveID = mod_mam_muc:archive_id_int(HostType, RoomJID), |
58 |
:-( |
QS = cowboy_req:parse_qs(Req), |
59 |
:-( |
PageSize = maybe_integer(proplists:get_value(<<"limit">>, QS, <<"50">>)), |
60 |
:-( |
Before = maybe_integer(proplists:get_value(<<"before">>, QS)), |
61 |
:-( |
End = maybe_before_to_us(Before, Now), |
62 |
:-( |
RSM = #rsm_in{direction = before, id = undefined}, |
63 |
:-( |
R = mod_mam_muc:lookup_messages(HostType, |
64 |
|
#{archive_id => ArchiveID, |
65 |
|
owner_jid => RoomJID, |
66 |
|
caller_jid => UserJID, |
67 |
|
rsm => RSM, |
68 |
|
borders => undefined, |
69 |
|
start_ts => undefined, |
70 |
|
end_ts => End, |
71 |
|
now => Now, |
72 |
|
with_jid => undefined, |
73 |
|
search_text => undefined, |
74 |
|
page_size => PageSize, |
75 |
|
limit_passed => true, |
76 |
|
max_result_limit => 50, |
77 |
|
is_simple => true}), |
78 |
:-( |
{ok, {_, _, Msgs}} = R, |
79 |
:-( |
JSONData = [make_json_item(Msg) || Msg <- Msgs], |
80 |
:-( |
{jiffy:encode(JSONData), Req, State}. |
81 |
|
|
82 |
|
from_json(Req, #{role_in_room := none} = State) -> |
83 |
1 |
mongoose_client_api:forbidden_request(Req, State); |
84 |
|
from_json(Req, #{user := User, jid := JID, room := Room} = State) -> |
85 |
7 |
{ok, Body, Req2} = cowboy_req:read_body(Req), |
86 |
7 |
case mongoose_client_api:json_to_map(Body) of |
87 |
|
{ok, JSONData} -> |
88 |
6 |
prepare_message_and_route_to_room(User, JID, Room, State, Req2, JSONData); |
89 |
|
_ -> |
90 |
1 |
mongoose_client_api:bad_request(Req2, <<"Request body is not a valid JSON">>, State) |
91 |
|
end. |
92 |
|
|
93 |
|
|
94 |
|
prepare_message_and_route_to_room(User, JID, Room, State, Req, JSONData) -> |
95 |
6 |
RoomJID = #jid{lserver = _} = maps:get(jid, Room), |
96 |
6 |
UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), |
97 |
6 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(JID#jid.lserver), |
98 |
6 |
case build_message_from_json(User, RoomJID, UUID, JSONData) of |
99 |
|
{ok, Message} -> |
100 |
3 |
Acc = mongoose_acc:new(#{ location => ?LOCATION, |
101 |
|
host_type => HostType, |
102 |
|
lserver => JID#jid.lserver, |
103 |
|
from_jid => JID, |
104 |
|
to_jid => RoomJID, |
105 |
|
element => Message }), |
106 |
3 |
ejabberd_router:route(JID, RoomJID, Acc, Message), |
107 |
3 |
Resp = #{id => UUID}, |
108 |
3 |
Req3 = cowboy_req:set_resp_body(jiffy:encode(Resp), Req), |
109 |
3 |
{true, Req3, State}; |
110 |
|
{error, ErrorMsg} -> |
111 |
3 |
Req2 = cowboy_req:set_resp_body(ErrorMsg, Req), |
112 |
3 |
mongoose_client_api:bad_request(Req2, State) |
113 |
|
end. |
114 |
|
|
115 |
|
-spec build_message_from_json(From :: binary(), To :: jid:jid(), ID :: binary(), JSON :: map()) -> |
116 |
|
{ok, exml:element()} | {error, ErrorMsg :: binary()}. |
117 |
|
build_message_from_json(From, To, ID, JSON) -> |
118 |
6 |
case build_children(JSON) of |
119 |
|
{error, _} = Err -> |
120 |
1 |
Err; |
121 |
|
[] -> |
122 |
2 |
{error, <<"No valid message elements">>}; |
123 |
|
Children -> |
124 |
3 |
Attrs = [{<<"from">>, From}, |
125 |
|
{<<"to">>, jid:to_binary(To)}, |
126 |
|
{<<"id">>, ID}, |
127 |
|
{<<"type">>, <<"groupchat">>}], |
128 |
3 |
{ok, #xmlel{name = <<"message">>, attrs = Attrs, children = Children}} |
129 |
|
end. |
130 |
|
|
131 |
|
build_children(JSON) -> |
132 |
6 |
lists:foldl(fun(_, {error, _} = Err) -> |
133 |
:-( |
Err; |
134 |
|
(ChildBuilder, Children) -> |
135 |
18 |
ChildBuilder(JSON, Children) |
136 |
|
end, [], [fun build_markable/2, fun build_marker/2, fun build_body/2]). |
137 |
|
|
138 |
|
build_body(#{ <<"body">> := Body }, Children) when is_binary(Body) -> |
139 |
3 |
[#xmlel{ name = <<"body">>, children = [#xmlcdata{ content = Body }] } | Children]; |
140 |
|
build_body(#{ <<"body">> := _Body }, _Children) -> |
141 |
1 |
{error, <<"Invalid body, it must be a string">>}; |
142 |
|
build_body(_JSON, Children) -> |
143 |
2 |
Children. |
144 |
|
|
145 |
|
build_marker(#{ <<"chat_marker">> := #{ <<"type">> := Type, <<"id">> := Id } }, Children) |
146 |
|
when Type == <<"received">>; |
147 |
|
Type == <<"displayed">>; |
148 |
|
Type == <<"acknowledged">> -> |
149 |
:-( |
[#xmlel{ name = Type, attrs = [{<<"xmlns">>, ?NS_CHAT_MARKERS}, {<<"id">>, Id}] } | Children]; |
150 |
|
build_marker(#{ <<"chat_marker">> := _Marker }, _Children) -> |
151 |
:-( |
{error, <<"Invalid marker, it must be 'received', 'displayed' or 'acknowledged'">>}; |
152 |
|
build_marker(_JSON, Children) -> |
153 |
6 |
Children. |
154 |
|
|
155 |
|
build_markable(#{ <<"body">> := _Body, <<"markable">> := true }, Children) -> |
156 |
:-( |
[#xmlel{ name = <<"markable">>, attrs = [{<<"xmlns">>, ?NS_CHAT_MARKERS}] } | Children]; |
157 |
|
build_markable(_JSON, Children) -> |
158 |
6 |
Children. |
159 |
|
|
160 |
|
-spec encode(Packet :: exml:element(), Timestamp :: integer()) -> map(). |
161 |
|
encode(Packet, Timestamp) -> |
162 |
2 |
From = exml_query:attr(Packet, <<"from">>), |
163 |
2 |
FromJID = jid:from_binary(From), |
164 |
2 |
Msg = make_json_item(Packet, FromJID, Timestamp), |
165 |
2 |
Msg#{room => FromJID#jid.luser}. |
166 |
|
|
167 |
|
-spec make_json_item(mod_mam:message_row()) -> term(). |
168 |
|
make_json_item(#{id := MAMID, jid := JID, packet := Msg}) -> |
169 |
:-( |
{Microsec, _} = mod_mam_utils:decode_compact_uuid(MAMID), |
170 |
:-( |
make_json_item(Msg, JID, Microsec div 1000). |
171 |
|
|
172 |
|
make_json_item(Msg, JID, Timestamp) -> |
173 |
2 |
Item = #{id => exml_query:attr(Msg, <<"id">>), |
174 |
|
from => make_from(JID), |
175 |
|
timestamp => Timestamp}, |
176 |
2 |
add_body_and_type(Item, Msg). |
177 |
|
|
178 |
|
make_from(#jid{lresource = <<>>} = JID) -> |
179 |
1 |
jid:to_binary(JID); |
180 |
|
make_from(#jid{lresource = Sender}) -> |
181 |
1 |
Sender. |
182 |
|
|
183 |
|
add_body_and_type(Item, Msg) -> |
184 |
2 |
case exml_query:path(Msg, [{element, <<"x">>}, {element, <<"user">>}]) of |
185 |
|
undefined -> |
186 |
1 |
add_regular_message_body(Item, Msg); |
187 |
|
#xmlel{} = AffChange -> |
188 |
1 |
add_aff_change_body(Item, AffChange) |
189 |
|
end. |
190 |
|
|
191 |
|
add_regular_message_body(Item0, Msg) -> |
192 |
1 |
Item1 = Item0#{type => <<"message">>}, |
193 |
1 |
Item2 = |
194 |
|
case exml_query:path(Msg, [{element, <<"body">>}, cdata]) of |
195 |
|
undefined -> |
196 |
:-( |
Item1; |
197 |
|
Body -> |
198 |
1 |
Item1#{body => Body} |
199 |
|
end, |
200 |
1 |
add_chat_marker(Item2, Msg). |
201 |
|
|
202 |
|
add_chat_marker(Item0, Msg) -> |
203 |
1 |
case exml_query:subelement_with_ns(Msg, ?NS_CHAT_MARKERS) of |
204 |
|
undefined -> |
205 |
1 |
Item0; |
206 |
|
#xmlel{ name = <<"markable">> } -> |
207 |
:-( |
Item0#{ markable => true }; |
208 |
|
#xmlel{ name = Type } = Marker -> |
209 |
:-( |
Item0#{ chat_marker => #{ type => Type, id => exml_query:attr(Marker, <<"id">>) } } |
210 |
|
end. |
211 |
|
|
212 |
|
add_aff_change_body(Item, #xmlel{attrs = Attrs} = User) -> |
213 |
1 |
Item#{type => <<"affiliation">>, |
214 |
|
affiliation => proplists:get_value(<<"affiliation">>, Attrs), |
215 |
|
user => exml_query:cdata(User)}. |