1 |
|
-module(mongoose_client_api_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 |
|
|
12 |
|
-export([to_json/2]). |
13 |
|
-export([send_message/2]). |
14 |
|
|
15 |
|
-export([encode/2]). |
16 |
|
|
17 |
|
-export([maybe_integer/1]). |
18 |
|
-export([maybe_before_to_us/2]). |
19 |
|
|
20 |
|
-ignore_xref([send_message/2, to_json/2, trails/0]). |
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_messages_doc:trails(). |
29 |
|
|
30 |
|
init(Req, Opts) -> |
31 |
2 |
mongoose_client_api:init(Req, Opts). |
32 |
|
|
33 |
|
is_authorized(Req, State) -> |
34 |
2 |
mongoose_client_api:is_authorized(Req, State). |
35 |
|
|
36 |
|
content_types_provided(Req, State) -> |
37 |
2 |
{[ |
38 |
|
{{<<"application">>, <<"json">>, '*'}, to_json} |
39 |
|
], Req, State}. |
40 |
|
|
41 |
|
content_types_accepted(Req, State) -> |
42 |
2 |
{[ |
43 |
|
{{<<"application">>, <<"json">>, '*'}, send_message} |
44 |
|
], Req, State}. |
45 |
|
|
46 |
|
allowed_methods(Req, State) -> |
47 |
2 |
{[<<"OPTIONS">>, <<"GET">>, <<"POST">>], Req, State}. |
48 |
|
|
49 |
|
to_json(Req, #{jid := JID} = State) -> |
50 |
:-( |
With = cowboy_req:binding(with, Req), |
51 |
:-( |
WithJID = maybe_jid(With), |
52 |
:-( |
maybe_to_json_with_jid(WithJID, JID, Req, State). |
53 |
|
|
54 |
|
maybe_to_json_with_jid(error, _, Req, State) -> |
55 |
:-( |
Req2 = cowboy_req:reply(404, Req), |
56 |
:-( |
{stop, Req2, State}; |
57 |
|
maybe_to_json_with_jid(WithJID, #jid{} = JID, Req, State = #{creds := Creds}) -> |
58 |
:-( |
HostType = mongoose_credentials:host_type(Creds), |
59 |
:-( |
Now = os:system_time(microsecond), |
60 |
:-( |
ArchiveID = mod_mam:archive_id_int(HostType, JID), |
61 |
:-( |
QS = cowboy_req:parse_qs(Req), |
62 |
:-( |
PageSize = maybe_integer(proplists:get_value(<<"limit">>, QS, <<"50">>)), |
63 |
:-( |
Before = maybe_integer(proplists:get_value(<<"before">>, QS)), |
64 |
:-( |
End = maybe_before_to_us(Before, Now), |
65 |
:-( |
RSM = #rsm_in{direction = before, id = undefined}, |
66 |
:-( |
R = mod_mam:lookup_messages(HostType, |
67 |
|
#{archive_id => ArchiveID, |
68 |
|
owner_jid => JID, |
69 |
|
rsm => RSM, |
70 |
|
borders => undefined, |
71 |
|
start_ts => undefined, |
72 |
|
end_ts => End, |
73 |
|
now => Now, |
74 |
|
with_jid => WithJID, |
75 |
|
search_text => undefined, |
76 |
|
page_size => PageSize, |
77 |
|
limit_passed => true, |
78 |
|
max_result_limit => 50, |
79 |
|
is_simple => true}), |
80 |
:-( |
{ok, {_, _, Msgs}} = R, |
81 |
:-( |
Resp = [make_json_msg(Msg, MAMId) || #{id := MAMId, packet := Msg} <- Msgs], |
82 |
:-( |
{jiffy:encode(Resp), Req, State}. |
83 |
|
|
84 |
|
send_message(Req, #{user := RawUser, jid := FromJID, creds := Creds} = State) -> |
85 |
2 |
{ok, Body, Req2} = cowboy_req:read_body(Req), |
86 |
2 |
case mongoose_client_api:json_to_map(Body) of |
87 |
|
{ok, #{<<"to">> := To, <<"body">> := MsgBody}} when is_binary(To), is_binary(MsgBody) -> |
88 |
2 |
ToJID = jid:from_binary(To), |
89 |
2 |
UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), |
90 |
2 |
XMLMsg0 = build_message(RawUser, To, UUID, MsgBody), |
91 |
2 |
Acc0 = mongoose_acc:new(#{ location => ?LOCATION, |
92 |
|
host_type => mongoose_credentials:host_type(Creds), |
93 |
|
lserver => FromJID#jid.lserver, |
94 |
|
from_jid => FromJID, |
95 |
|
to_jid => ToJID, |
96 |
|
element => XMLMsg0 }), |
97 |
2 |
Acc1 = mongoose_hooks:rest_user_send_packet(Acc0, FromJID, ToJID, XMLMsg0), |
98 |
2 |
XMLMsg1 = mongoose_acc:element(Acc1), |
99 |
2 |
ejabberd_router:route(FromJID, ToJID, Acc1, XMLMsg1), |
100 |
2 |
Resp = #{<<"id">> => UUID}, |
101 |
2 |
Req3 = cowboy_req:set_resp_body(jiffy:encode(Resp), Req2), |
102 |
2 |
{true, Req3, State}; |
103 |
|
_ -> |
104 |
:-( |
mongoose_client_api:bad_request(Req2, State) |
105 |
|
end. |
106 |
|
|
107 |
|
build_message(From, To, Id, Body) -> |
108 |
2 |
Attrs = [{<<"from">>, From}, |
109 |
|
{<<"to">>, To}, |
110 |
|
{<<"id">>, Id}, |
111 |
|
{<<"type">>, <<"chat">>}], |
112 |
2 |
#xmlel{name = <<"message">>, |
113 |
|
attrs = Attrs, |
114 |
|
children = [#xmlel{name = <<"body">>, |
115 |
|
children = [#xmlcdata{content = Body}]}]}. |
116 |
|
|
117 |
|
make_json_msg(Msg, MAMId) -> |
118 |
:-( |
{Microsec, _} = mod_mam_utils:decode_compact_uuid(MAMId), |
119 |
:-( |
encode(Msg, Microsec div 1000). |
120 |
|
|
121 |
|
-spec encode(exml:item(), integer()) -> map(). |
122 |
|
encode(Msg, Timestamp) -> |
123 |
|
|
124 |
|
%Smack library specific query for properties. |
125 |
1 |
RawMsgProps = exml_query:subelement_with_name_and_ns( |
126 |
|
Msg, |
127 |
|
<<"properties">>, |
128 |
|
<<"http://www.jivesoftware.com/xmlns/xmpp/properties">>), |
129 |
|
|
130 |
1 |
BodyTag = exml_query:path(Msg, [{element, <<"body">>}]), |
131 |
1 |
ExtensionList = |
132 |
|
case RawMsgProps of |
133 |
|
#xmlel{children = Children} -> |
134 |
:-( |
Props = [convert_prop_child(Child) || Child <- Children], |
135 |
:-( |
[{<<"properties">>, maps:from_list(Props)}]; |
136 |
|
_ -> |
137 |
1 |
[] |
138 |
|
end, |
139 |
1 |
Thread = exml_query:path(Msg, [{element, <<"thread">>}, cdata]), |
140 |
1 |
ThreadParent = exml_query:path(Msg, [{element, <<"thread">>}, {attr, <<"parent">>}]), |
141 |
1 |
ThreadAndThreadParentList = case {Thread, ThreadParent} of |
142 |
|
{undefined, undefined} -> |
143 |
1 |
[]; |
144 |
|
{Thread, undefined} -> |
145 |
:-( |
[{<<"thread">>, Thread}]; |
146 |
|
{Thread, ThreadParent} -> |
147 |
:-( |
[{<<"thread">>, Thread}, {<<"parent">>, ThreadParent}] |
148 |
|
end, |
149 |
1 |
L = [{<<"from">>, exml_query:attr(Msg, <<"from">>)}, |
150 |
|
{<<"to">>, exml_query:attr(Msg, <<"to">>)}, |
151 |
|
{<<"id">>, exml_query:attr(Msg, <<"id">>)}, |
152 |
|
{<<"body">>, exml_query:cdata(BodyTag)}, |
153 |
|
{<<"timestamp">>, Timestamp} | ExtensionList] ++ ThreadAndThreadParentList, |
154 |
1 |
maps:from_list(L). |
155 |
|
|
156 |
|
convert_prop_child(Child)-> |
157 |
:-( |
Name = exml_query:path(Child, [{element, <<"name">>}, cdata]), |
158 |
:-( |
Value = exml_query:path(Child, [{element, <<"value">>}, cdata]), |
159 |
:-( |
{Name, Value}. |
160 |
|
|
161 |
|
maybe_jid(undefined) -> |
162 |
:-( |
undefined; |
163 |
|
maybe_jid(JID) -> |
164 |
:-( |
jid:from_binary(JID). |
165 |
|
|
166 |
|
maybe_integer(undefined) -> |
167 |
:-( |
undefined; |
168 |
|
maybe_integer(Val) -> |
169 |
:-( |
binary_to_integer(Val). |
170 |
|
|
171 |
|
maybe_before_to_us(undefined, Now) -> |
172 |
:-( |
Now; |
173 |
|
maybe_before_to_us(Timestamp, _) -> |
174 |
:-( |
Timestamp * 1000. |