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