1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : mod_muc_light_codec_modern.erl |
3 |
|
%%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com> |
4 |
|
%%% Purpose : MUC Light codec for modern syntax |
5 |
|
%%% Created : 29 Sep 2015 by Piotr Nosek <piotr.nosek@erlang-solutions.com> |
6 |
|
%%%---------------------------------------------------------------------- |
7 |
|
|
8 |
|
-module(mod_muc_light_codec_modern). |
9 |
|
-author('piotr.nosek@erlang-solutions.com'). |
10 |
|
|
11 |
|
-behaviour(mod_muc_light_codec_backend). |
12 |
|
|
13 |
|
%% API |
14 |
|
-export([decode/4, encode/5, encode_error/5]). |
15 |
|
|
16 |
|
-include("mongoose.hrl"). |
17 |
|
-include("jlib.hrl"). |
18 |
|
-include("mod_muc_light.hrl"). |
19 |
|
|
20 |
|
-define(HANDLE_PARSE_ERROR(Function, From, IQ), |
21 |
|
{error, Reason} -> |
22 |
|
?LOG_WARNING(#{what => muc_parse_failed, parser => Function, |
23 |
|
iq => IQ, from_jid => jid:to_binary(From), |
24 |
|
reason => Reason}), |
25 |
|
{error, bad_request} |
26 |
|
catch Class:Reason:Stacktrace -> |
27 |
|
?LOG_WARNING(#{what => muc_parse_failed, parser => Function, |
28 |
|
iq => IQ, from_jid => jid:to_binary(From), |
29 |
|
class => Class, reason => Reason, stacktrace => Stacktrace}), |
30 |
|
{error, bad_request}). |
31 |
|
|
32 |
|
-type bad_request() :: bad_request | {bad_request, binary()}. |
33 |
|
|
34 |
|
%%==================================================================== |
35 |
|
%% API |
36 |
|
%%==================================================================== |
37 |
|
|
38 |
|
-spec decode(From :: jid:jid(), To :: jid:jid(), Stanza :: exml:element(), |
39 |
|
Acc :: mongoose_acc:t()) -> |
40 |
|
mod_muc_light_codec_backend:decode_result(). |
41 |
|
decode(_From, #jid{ lresource = Resource }, _Stanza, _Acc) when Resource =/= <<>> -> |
42 |
1 |
{error, {bad_request, <<"Resource expected to be empty">>}}; |
43 |
|
decode(_From, _To, #xmlel{ name = <<"message">> } = Stanza, _Acc) -> |
44 |
423 |
decode_message(Stanza); |
45 |
|
decode(From, _To, #xmlel{ name = <<"iq">> } = Stanza, _Acc) -> |
46 |
434 |
decode_iq(From, jlib:iq_query_info(Stanza)); |
47 |
|
decode(_, _, _, _) -> |
48 |
:-( |
{error, {bad_request, <<"Failed to decode unknown format">>}}. |
49 |
|
|
50 |
|
-spec encode(Request :: muc_light_encode_request(), OriginalSender :: jid:jid(), |
51 |
|
RoomJID :: jid:jid(), |
52 |
|
HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler(), |
53 |
|
Acc :: mongoose_acc:t()) -> mongoose_acc:t(). |
54 |
|
encode({#msg{} = Msg, AffUsers}, Sender, RoomBareJid, HandleFun, Acc) -> |
55 |
416 |
US = jid:to_lus(Sender), |
56 |
416 |
FromNick = jid:to_binary(US), |
57 |
416 |
Aff = get_sender_aff(AffUsers, US), |
58 |
416 |
{RoomJID, RoomBin} = jids_from_room_with_resource(RoomBareJid, FromNick), |
59 |
416 |
Attrs = [ |
60 |
|
{<<"id">>, Msg#msg.id}, |
61 |
|
{<<"type">>, <<"groupchat">>}, |
62 |
|
{<<"from">>, RoomBin} |
63 |
|
], |
64 |
416 |
MsgForArch = #xmlel{ name = <<"message">>, attrs = Attrs, children = Msg#msg.children }, |
65 |
416 |
TS = mongoose_acc:timestamp(Acc), |
66 |
416 |
EventData = #{from_nick => FromNick, |
67 |
|
from_jid => Sender, |
68 |
|
room_jid => RoomBareJid, |
69 |
|
affiliation => Aff, |
70 |
|
role => mod_muc_light_utils:light_aff_to_muc_role(Aff), |
71 |
|
timestamp => TS}, |
72 |
416 |
HostType = mod_muc_light_utils:acc_to_host_type(Acc), |
73 |
416 |
Packet1 = #xmlel{ children = Children } |
74 |
|
= mongoose_hooks:filter_room_packet(HostType, MsgForArch, EventData), |
75 |
416 |
lists:foreach( |
76 |
|
fun({{U, S}, _}) -> |
77 |
1026 |
msg_to_aff_user(RoomJID, U, S, Attrs, Children, HandleFun) |
78 |
|
end, AffUsers), |
79 |
416 |
mongoose_acc:update_stanza(#{from_jid => RoomJID, |
80 |
|
to_jid => RoomBareJid, |
81 |
|
element => Packet1}, Acc); |
82 |
|
encode(OtherCase, Sender, RoomBareJid, HandleFun, Acc) -> |
83 |
604 |
{RoomJID, RoomBin} = jids_from_room_with_resource(RoomBareJid, <<>>), |
84 |
604 |
case encode_iq(OtherCase, Sender, RoomJID, RoomBin, HandleFun, Acc) of |
85 |
|
{reply, ID} -> |
86 |
400 |
IQRes = make_iq_result(RoomBin, jid:to_binary(Sender), ID, <<>>, undefined), |
87 |
400 |
HandleFun(RoomJID, Sender, IQRes); |
88 |
|
{reply, XMLNS, Els, ID} -> |
89 |
41 |
IQRes = make_iq_result(RoomBin, jid:to_binary(Sender), ID, XMLNS, Els), |
90 |
41 |
HandleFun(RoomJID, Sender, IQRes); |
91 |
|
{reply, FromJID, FromBin, XMLNS, Els, ID} -> |
92 |
163 |
IQRes = make_iq_result(FromBin, jid:to_binary(Sender), ID, XMLNS, Els), |
93 |
163 |
HandleFun(FromJID, Sender, IQRes) |
94 |
|
end. |
95 |
|
|
96 |
|
get_sender_aff(Users, US) -> |
97 |
416 |
case lists:keyfind(US, 1, Users) of |
98 |
416 |
{US, Aff} -> Aff; |
99 |
:-( |
_ -> undefined |
100 |
|
end. |
101 |
|
|
102 |
|
-spec encode_error( |
103 |
|
ErrMsg :: tuple(), OrigFrom :: jid:jid(), OrigTo :: jid:jid(), |
104 |
|
OrigPacket :: exml:element(), Acc :: mongoose_acc:t()) -> |
105 |
|
mongoose_acc:t(). |
106 |
|
encode_error(ErrMsg, OrigFrom, OrigTo, OrigPacket, Acc) -> |
107 |
16 |
mod_muc_light_codec_backend:encode_error(ErrMsg, [], OrigFrom, OrigTo, OrigPacket, Acc). |
108 |
|
|
109 |
|
%%==================================================================== |
110 |
|
%% Message decoding |
111 |
|
%%==================================================================== |
112 |
|
|
113 |
|
-spec decode_message(Packet :: exml:element()) -> |
114 |
|
{ok, muc_light_packet()} | {error, bad_request} | ignore. |
115 |
|
decode_message(#xmlel{ attrs = Attrs, children = Children }) -> |
116 |
423 |
decode_message_by_type(lists:keyfind(<<"type">>, 1, Attrs), |
117 |
|
lists:keyfind(<<"id">>, 1, Attrs), Children). |
118 |
|
|
119 |
|
-spec decode_message_by_type(Type :: {binary(), binary()} | false, |
120 |
|
Id :: {binary(), binary()} | false, |
121 |
|
Children :: [jlib:xmlch()]) -> |
122 |
|
{ok, msg()} | {error, bad_request} | ignore. |
123 |
|
decode_message_by_type({_, <<"groupchat">>}, Id, Children) -> |
124 |
418 |
{ok, #msg{ children = Children, id = ensure_id(Id) }}; |
125 |
|
decode_message_by_type({_, <<"error">>}, _, _) -> |
126 |
5 |
ignore; |
127 |
|
decode_message_by_type(_, _, _) -> |
128 |
:-( |
{error, bad_request}. |
129 |
|
|
130 |
|
-spec ensure_id(Id :: {binary(), binary()} | false) -> binary(). |
131 |
276 |
ensure_id(false) -> mongoose_bin:gen_from_timestamp(); |
132 |
142 |
ensure_id({_, Id}) -> Id. |
133 |
|
|
134 |
|
%%==================================================================== |
135 |
|
%% IQ decoding |
136 |
|
%%==================================================================== |
137 |
|
|
138 |
|
-spec decode_iq(From :: jid:jid(), IQ :: jlib:iq()) -> |
139 |
|
{ok, muc_light_packet() | muc_light_disco() | jlib:iq()} | {error, bad_request()} | ignore. |
140 |
|
decode_iq(_From, #iq{ xmlns = ?NS_MUC_LIGHT_CONFIGURATION, type = get, |
141 |
|
sub_el = QueryEl, id = ID }) -> |
142 |
12 |
Version = exml_query:path(QueryEl, [{element, <<"version">>}, cdata], <<>>), |
143 |
12 |
{ok, {get, #config{ id = ID, prev_version = Version }}}; |
144 |
|
decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_LIGHT_CONFIGURATION, type = set, |
145 |
|
sub_el = #xmlel{ children = QueryEls }, id = ID }) -> |
146 |
14 |
try parse_config(QueryEls) of |
147 |
|
{ok, RawConfig} -> |
148 |
14 |
{ok, {set, #config{ id = ID, raw_config = RawConfig }}}; |
149 |
:-( |
?HANDLE_PARSE_ERROR(parse_config, From, IQ) |
150 |
|
end; |
151 |
|
decode_iq(_From, #iq{ xmlns = ?NS_MUC_LIGHT_AFFILIATIONS, type = get, |
152 |
|
sub_el = QueryEl, id = ID }) -> |
153 |
6 |
Version = exml_query:path(QueryEl, [{element, <<"version">>}, cdata], <<>>), |
154 |
6 |
{ok, {get, #affiliations{ id = ID, prev_version = Version }}}; |
155 |
|
decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_LIGHT_AFFILIATIONS, type = set, |
156 |
|
sub_el = #xmlel{ children = QueryEls }, id = ID }) -> |
157 |
148 |
try parse_aff_users(QueryEls) of |
158 |
|
{ok, AffUsers} -> |
159 |
148 |
{ok, {set, #affiliations{ id = ID, aff_users = AffUsers }}}; |
160 |
:-( |
?HANDLE_PARSE_ERROR(parse_aff_users, From, IQ) |
161 |
|
end; |
162 |
|
decode_iq(_From, #iq{ xmlns = ?NS_MUC_LIGHT_INFO, type = get, sub_el = QueryEl, id = ID }) -> |
163 |
3 |
Version = exml_query:path(QueryEl, [{element, <<"version">>}, cdata], <<>>), |
164 |
3 |
{ok, {get, #info{ |
165 |
|
id = ID, |
166 |
|
prev_version = Version |
167 |
|
}}}; |
168 |
|
decode_iq(_From, #iq{ xmlns = ?NS_MUC_LIGHT_BLOCKING, type = get, id = ID }) -> |
169 |
4 |
{ok, {get, #blocking{ id = ID }}}; |
170 |
|
decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_LIGHT_BLOCKING, type = set, |
171 |
|
sub_el = #xmlel{ children = QueryEls }, id = ID }) -> |
172 |
18 |
try parse_blocking_list(QueryEls) of |
173 |
|
{ok, BlockingList} -> |
174 |
18 |
{ok, {set, #blocking{ id = ID, items = BlockingList }}}; |
175 |
:-( |
?HANDLE_PARSE_ERROR(parse_blocking_list, From, IQ) |
176 |
|
end; |
177 |
|
decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_LIGHT_CREATE, type = set, sub_el = QueryEl, id = ID }) -> |
178 |
166 |
ConfigEl = exml_query:path(QueryEl, [{element, <<"configuration">>}]), |
179 |
166 |
OccupantsEl = exml_query:path(QueryEl, [{element, <<"occupants">>}]), |
180 |
166 |
try parse_config(safe_get_children(ConfigEl)) of |
181 |
|
{ok, RawConfig} -> |
182 |
166 |
try parse_aff_users(safe_get_children(OccupantsEl)) of |
183 |
|
{ok, AffUsers} -> |
184 |
166 |
{ok, {set, #create{ id = ID, raw_config = RawConfig, aff_users = AffUsers }}}; |
185 |
:-( |
?HANDLE_PARSE_ERROR(parse_aff_users, From, IQ) |
186 |
|
end; |
187 |
:-( |
?HANDLE_PARSE_ERROR(parse_config, From, IQ) |
188 |
|
end; |
189 |
|
decode_iq(_From, #iq{ xmlns = ?NS_MUC_LIGHT_DESTROY, type = set, id = ID }) -> |
190 |
6 |
{ok, {set, #destroy{ id = ID }}}; |
191 |
|
decode_iq(_From, #iq{ xmlns = ?NS_DISCO_ITEMS, type = get, id = ID} = IQ) -> |
192 |
17 |
{ok, {get, #disco_items{ id = ID, rsm = jlib:rsm_decode(IQ) }}}; |
193 |
|
decode_iq(_From, #iq{ xmlns = ?NS_DISCO_INFO, type = get, id = ID}) -> |
194 |
4 |
{ok, {get, #disco_info{ id = ID }}}; |
195 |
|
decode_iq(_From, #iq{ type = error }) -> |
196 |
:-( |
ignore; |
197 |
|
decode_iq(_From, #iq{} = IQ) -> |
198 |
36 |
{ok, IQ}; |
199 |
|
decode_iq(_, _) -> |
200 |
:-( |
{error, {bad_request, <<"Unknown IQ format">>}}. |
201 |
|
|
202 |
|
%% ------------------ Parsers ------------------ |
203 |
|
|
204 |
|
-spec parse_config(Els :: [jlib:xmlch()]) -> {ok, mod_muc_light_room_config:binary_kv()} |
205 |
|
| {error, bad_request()}. |
206 |
|
parse_config(Els) -> |
207 |
180 |
parse_config(Els, []). |
208 |
|
|
209 |
|
-spec parse_config(Els :: [jlib:xmlch()], ConfigAcc :: mod_muc_light_room_config:binary_kv()) -> |
210 |
|
{ok, mod_muc_light_room_config:binary_kv()} | {error, bad_request()}. |
211 |
|
parse_config([], ConfigAcc) -> |
212 |
180 |
{ok, ConfigAcc}; |
213 |
|
parse_config([#xmlel{ name = <<"version">> } | _], _) -> |
214 |
:-( |
{error, {bad_request, <<"Version element not allowed">>}}; |
215 |
|
parse_config([#xmlel{ name = Key, children = [ #xmlcdata{ content = Value } ] } | REls], |
216 |
|
ConfigAcc) -> |
217 |
67 |
parse_config(REls, [{Key, Value} | ConfigAcc]); |
218 |
|
parse_config([_ | REls], ConfigAcc) -> |
219 |
:-( |
parse_config(REls, ConfigAcc). |
220 |
|
|
221 |
|
-spec parse_aff_users(Els :: [jlib:xmlch()]) -> |
222 |
|
{ok, aff_users()} | {error, bad_request()}. |
223 |
|
parse_aff_users(Els) -> |
224 |
314 |
parse_aff_users(Els, []). |
225 |
|
|
226 |
|
-spec parse_aff_users(Els :: [jlib:xmlch()], AffUsersAcc :: aff_users()) -> |
227 |
|
{ok, aff_users()} | {error, bad_request()}. |
228 |
|
parse_aff_users([], AffUsersAcc) -> |
229 |
314 |
{ok, AffUsersAcc}; |
230 |
|
parse_aff_users([#xmlcdata{} | RItemsEls], AffUsersAcc) -> |
231 |
:-( |
parse_aff_users(RItemsEls, AffUsersAcc); |
232 |
|
parse_aff_users([#xmlel{ name = <<"user">>, attrs = [{<<"affiliation">>, AffBin}], |
233 |
|
children = [ #xmlcdata{ content = JIDBin } ] } | RItemsEls], |
234 |
|
AffUsersAcc) -> |
235 |
353 |
#jid{} = JID = jid:from_binary(JIDBin), |
236 |
353 |
Aff = mod_muc_light_utils:b2aff(AffBin), |
237 |
353 |
parse_aff_users(RItemsEls, [{jid:to_lus(JID), Aff} | AffUsersAcc]); |
238 |
|
parse_aff_users(_, _) -> |
239 |
:-( |
{error, {bad_request, <<"Failed to parse affiliations">>}}. |
240 |
|
|
241 |
|
-spec parse_blocking_list(Els :: [jlib:xmlch()]) -> {ok, [blocking_item()]} | {error, bad_request}. |
242 |
|
parse_blocking_list(ItemsEls) -> |
243 |
18 |
parse_blocking_list(ItemsEls, []). |
244 |
|
|
245 |
|
-spec parse_blocking_list(Els :: [jlib:xmlch()], ItemsAcc :: [blocking_item()]) -> |
246 |
|
{ok, [blocking_item()]} | {error, bad_request}. |
247 |
|
parse_blocking_list([], ItemsAcc) -> |
248 |
18 |
{ok, ItemsAcc}; |
249 |
|
parse_blocking_list([#xmlel{ name = WhatBin, attrs = [{<<"action">>, ActionBin}], |
250 |
|
children = [ #xmlcdata{ content = JIDBin } ] } | RItemsEls], |
251 |
|
ItemsAcc) -> |
252 |
22 |
#jid{} = JID = jid:from_binary(JIDBin), |
253 |
22 |
Action = b2action(ActionBin), |
254 |
22 |
What = b2what(WhatBin), |
255 |
22 |
parse_blocking_list(RItemsEls, [{What, Action, jid:to_lus(JID)} | ItemsAcc]); |
256 |
|
parse_blocking_list(_, _) -> |
257 |
:-( |
{error, bad_request}. |
258 |
|
|
259 |
|
%%==================================================================== |
260 |
|
%% Encoding |
261 |
|
%%==================================================================== |
262 |
|
|
263 |
|
encode_iq({get, #disco_info{ id = ID }}, Sender, RoomJID, _RoomBin, _HandleFun, Acc) -> |
264 |
4 |
HostType = mod_muc_light_utils:acc_to_host_type(Acc), |
265 |
4 |
IdentityXML = mongoose_disco:identities_to_xml([identity()]), |
266 |
4 |
FeatureXML = mongoose_disco:get_muc_features(HostType, Sender, RoomJID, <<>>, <<>>, |
267 |
|
[?NS_MUC_LIGHT]), |
268 |
4 |
DiscoEls = IdentityXML ++ FeatureXML, |
269 |
4 |
{reply, ?NS_DISCO_INFO, DiscoEls, ID}; |
270 |
|
encode_iq({get, #disco_items{ rooms = Rooms, id = ID, rsm = RSMOut }}, |
271 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
272 |
16 |
DiscoEls = [ #xmlel{ name = <<"item">>, |
273 |
|
attrs = [{<<"jid">>, <<RoomU/binary, $@, RoomS/binary>>}, |
274 |
|
{<<"name">>, RoomName}, |
275 |
|
{<<"version">>, RoomVersion}] } |
276 |
16 |
|| {{RoomU, RoomS}, RoomName, RoomVersion} <- Rooms ], |
277 |
16 |
{reply, ?NS_DISCO_ITEMS, jlib:rsm_encode(RSMOut) ++ DiscoEls, ID}; |
278 |
|
encode_iq({get, #config{ prev_version = SameVersion, version = SameVersion, id = ID }}, |
279 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
280 |
1 |
{reply, ID}; |
281 |
|
encode_iq({get, #config{} = Config}, |
282 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
283 |
11 |
ConfigEls = [ kv_to_el(Field) || Field <- [{<<"version">>, Config#config.version} |
284 |
|
| Config#config.raw_config] ], |
285 |
11 |
{reply, ?NS_MUC_LIGHT_CONFIGURATION, ConfigEls, Config#config.id}; |
286 |
|
encode_iq({get, #affiliations{ prev_version = SameVersion, version = SameVersion, id = ID }}, |
287 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
288 |
2 |
{reply, ID}; |
289 |
|
encode_iq({get, #affiliations{ version = Version } = Affs}, |
290 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
291 |
4 |
AffEls = [ aff_user_to_el(AffUser) || AffUser <- Affs#affiliations.aff_users ], |
292 |
4 |
{reply, ?NS_MUC_LIGHT_AFFILIATIONS, [kv_to_el(<<"version">>, Version) | AffEls], |
293 |
|
Affs#affiliations.id}; |
294 |
|
encode_iq({get, #info{ prev_version = SameVersion, version = SameVersion, id = ID }}, |
295 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
296 |
:-( |
{reply, ID}; |
297 |
|
encode_iq({get, #info{ version = Version } = Info}, |
298 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
299 |
3 |
ConfigEls = [ kv_to_el(Field) || Field <- Info#info.raw_config ], |
300 |
3 |
AffEls = [ aff_user_to_el(AffUser) || AffUser <- Info#info.aff_users ], |
301 |
3 |
InfoEls = [ |
302 |
|
kv_to_el(<<"version">>, Version), |
303 |
|
#xmlel{ name = <<"configuration">>, children = ConfigEls }, |
304 |
|
#xmlel{ name = <<"occupants">>, children = AffEls } |
305 |
|
], |
306 |
3 |
{reply, ?NS_MUC_LIGHT_INFO, InfoEls, Info#info.id}; |
307 |
|
encode_iq({set, #affiliations{} = Affs, OldAffUsers, NewAffUsers}, |
308 |
|
_Sender, RoomJID, RoomBin, HandleFun, Acc) -> |
309 |
362 |
Attrs = [ |
310 |
|
{<<"id">>, Affs#affiliations.id}, |
311 |
|
{<<"type">>, <<"groupchat">>}, |
312 |
|
{<<"from">>, RoomBin} |
313 |
|
], |
314 |
|
|
315 |
362 |
AllAffsEls = [ aff_user_to_el(AffUser) || AffUser <- Affs#affiliations.aff_users ], |
316 |
362 |
VersionEl = kv_to_el(<<"version">>, Affs#affiliations.version), |
317 |
362 |
NotifForCurrentNoPrevVersion = [ VersionEl | AllAffsEls ], |
318 |
362 |
MsgForArch = #xmlel{ name = <<"message">>, attrs = Attrs, |
319 |
|
children = msg_envelope(?NS_MUC_LIGHT_AFFILIATIONS, |
320 |
|
NotifForCurrentNoPrevVersion) }, |
321 |
362 |
EventData = room_event(Acc, RoomJID), |
322 |
362 |
HostType = mod_muc_light_utils:acc_to_host_type(Acc), |
323 |
362 |
#xmlel{children = FinalChildrenForCurrentNoPrevVersion} |
324 |
|
= mongoose_hooks:filter_room_packet(HostType, MsgForArch, EventData), |
325 |
362 |
FinalChildrenForCurrent = inject_prev_version(FinalChildrenForCurrentNoPrevVersion, |
326 |
|
Affs#affiliations.prev_version), |
327 |
362 |
bcast_aff_messages(RoomJID, OldAffUsers, NewAffUsers, Attrs, VersionEl, |
328 |
|
FinalChildrenForCurrent, HandleFun), |
329 |
|
|
330 |
362 |
{reply, Affs#affiliations.id}; |
331 |
|
encode_iq({get, #blocking{} = Blocking}, |
332 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
333 |
3 |
BlockingEls = [ blocking_to_el(BlockingItem) || BlockingItem <- Blocking#blocking.items ], |
334 |
3 |
{reply, ?NS_MUC_LIGHT_BLOCKING, BlockingEls, Blocking#blocking.id}; |
335 |
|
encode_iq({set, #blocking{ id = ID }}, |
336 |
|
_Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> |
337 |
17 |
{reply, ID}; |
338 |
|
encode_iq({set, #create{} = Create, UniqueRequested}, |
339 |
|
_Sender, RoomJID, RoomBin, HandleFun, Acc) -> |
340 |
163 |
Attrs = [ |
341 |
|
{<<"id">>, Create#create.id}, |
342 |
|
{<<"type">>, <<"groupchat">>}, |
343 |
|
{<<"from">>, RoomBin} |
344 |
|
], |
345 |
|
|
346 |
163 |
VersionEl = kv_to_el(<<"version">>, Create#create.version), |
347 |
163 |
bcast_aff_messages(RoomJID, [], Create#create.aff_users, Attrs, VersionEl, [], HandleFun), |
348 |
|
|
349 |
163 |
AllAffsEls = [ aff_user_to_el(AffUser) || AffUser <- Create#create.aff_users ], |
350 |
163 |
MsgForArch = #xmlel{ name = <<"message">>, attrs = Attrs, |
351 |
|
children = msg_envelope(?NS_MUC_LIGHT_AFFILIATIONS, AllAffsEls) }, |
352 |
163 |
EventData = room_event(Acc, RoomJID), |
353 |
163 |
HostType = mod_muc_light_utils:acc_to_host_type(Acc), |
354 |
163 |
mongoose_hooks:filter_room_packet(HostType, MsgForArch, EventData), |
355 |
|
|
356 |
|
%% IQ reply "from" |
357 |
|
%% Sent from service JID when unique room was requested |
358 |
163 |
{ResFromJID, ResFromBin} = case UniqueRequested of |
359 |
7 |
true -> {#jid{lserver = RoomJID#jid.lserver}, |
360 |
|
RoomJID#jid.lserver}; |
361 |
156 |
false -> {RoomJID, RoomBin} |
362 |
|
end, |
363 |
163 |
{reply, ResFromJID, ResFromBin, <<>>, undefined, Create#create.id}; |
364 |
|
encode_iq({set, #destroy{ id = ID }, AffUsers}, |
365 |
|
_Sender, RoomJID, RoomBin, HandleFun, _Acc) -> |
366 |
6 |
Attrs = [ |
367 |
|
{<<"id">>, ID}, |
368 |
|
{<<"type">>, <<"groupchat">>}, |
369 |
|
{<<"from">>, RoomBin} |
370 |
|
], |
371 |
|
|
372 |
6 |
lists:foreach( |
373 |
|
fun({{U, S}, _}) -> |
374 |
15 |
NoneAffEnveloped = msg_envelope(?NS_MUC_LIGHT_AFFILIATIONS, |
375 |
|
[aff_user_to_el({{U, S}, none})]), |
376 |
15 |
DestroyEnveloped = [ #xmlel{ name = <<"x">>, |
377 |
|
attrs = [{<<"xmlns">>, ?NS_MUC_LIGHT_DESTROY}] } |
378 |
|
| NoneAffEnveloped ], |
379 |
15 |
msg_to_aff_user(RoomJID, U, S, Attrs, DestroyEnveloped, HandleFun) |
380 |
|
end, AffUsers), |
381 |
|
|
382 |
6 |
{reply, ID}; |
383 |
|
encode_iq({set, #config{} = Config, AffUsers}, |
384 |
|
_Sender, RoomJID, RoomBin, HandleFun, Acc) -> |
385 |
12 |
MsgForArch = encode_set_config(Config, RoomBin), |
386 |
12 |
EventData = room_event(Acc, RoomJID), |
387 |
12 |
HostType = mod_muc_light_utils:acc_to_host_type(Acc), |
388 |
12 |
#xmlel{ children = FinalConfigNotif } |
389 |
|
= mongoose_hooks:filter_room_packet(HostType, MsgForArch, EventData), |
390 |
|
|
391 |
12 |
lists:foreach( |
392 |
|
fun({{U, S}, _}) -> |
393 |
36 |
msg_to_aff_user(RoomJID, U, S, MsgForArch#xmlel.attrs, FinalConfigNotif, HandleFun) |
394 |
|
end, AffUsers), |
395 |
|
|
396 |
12 |
{reply, Config#config.id}. |
397 |
|
|
398 |
|
encode_set_config(Config, RoomBin) -> |
399 |
12 |
Attrs = [ |
400 |
|
{<<"id">>, Config#config.id}, |
401 |
|
{<<"type">>, <<"groupchat">>}, |
402 |
|
{<<"from">>, RoomBin} |
403 |
|
], |
404 |
12 |
ConfigEls = [ kv_to_el(ConfigField) || ConfigField <- Config#config.raw_config ], |
405 |
12 |
ConfigNotif = [ kv_to_el(<<"prev-version">>, Config#config.prev_version), |
406 |
|
kv_to_el(<<"version">>, Config#config.version) |
407 |
|
| ConfigEls ], |
408 |
12 |
#xmlel{name = <<"message">>, attrs = Attrs, |
409 |
|
children = msg_envelope(?NS_MUC_LIGHT_CONFIGURATION, ConfigNotif) }. |
410 |
|
|
411 |
|
%% --------------------------- Helpers --------------------------- |
412 |
|
|
413 |
|
-spec identity() -> mongoose_disco:identity(). |
414 |
|
identity() -> |
415 |
4 |
#{category => <<"conference">>, type => <<"text">>, name => <<"MUC Light (modern)">>}. |
416 |
|
|
417 |
|
-spec aff_user_to_el(aff_user()) -> exml:element(). |
418 |
|
aff_user_to_el({User, Aff}) -> |
419 |
1533 |
#xmlel{ name = <<"user">>, |
420 |
|
attrs = [{<<"affiliation">>, mod_muc_light_utils:aff2b(Aff)}], |
421 |
|
children = [#xmlcdata{ content = jid:to_binary(User) }] }. |
422 |
|
|
423 |
|
-spec blocking_to_el(blocking_item()) -> exml:element(). |
424 |
|
blocking_to_el({What, Action, Who}) -> |
425 |
2 |
#xmlel{ name = what2b(What), |
426 |
|
attrs = [{<<"action">>, action2b(Action)}], |
427 |
|
children = [#xmlcdata{ content = jid:to_binary(Who) }] }. |
428 |
|
|
429 |
|
-spec kv_to_el({binary(), binary()}) -> exml:element(). |
430 |
|
kv_to_el({Key, Value}) -> |
431 |
55 |
kv_to_el(Key, Value). |
432 |
|
|
433 |
|
-spec kv_to_el(binary(), binary()) -> exml:element(). |
434 |
|
kv_to_el(Key, Value) -> |
435 |
973 |
#xmlel{ name = Key, children = [#xmlcdata{ content = Value }] }. |
436 |
|
|
437 |
|
-spec msg_envelope(XMLNS :: binary(), Children :: [jlib:xmlch()]) -> [exml:element()]. |
438 |
|
msg_envelope(XMLNS, Children) -> |
439 |
1273 |
[ #xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, XMLNS}], children = Children }, |
440 |
|
#xmlel{ name = <<"body">> } ]. |
441 |
|
|
442 |
|
-spec inject_prev_version(IQChildren :: [jlib:xmlch()], PrevVersion :: binary()) -> [jlib:xmlch()]. |
443 |
|
inject_prev_version([#xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC_LIGHT_AFFILIATIONS}], |
444 |
|
children = Items} = XEl | REls], PrevVersion) -> |
445 |
362 |
[XEl#xmlel{ children = [kv_to_el(<<"prev-version">>, PrevVersion) | Items] } | REls]; |
446 |
|
inject_prev_version([El | REls], PrevVersion) -> |
447 |
:-( |
[El | inject_prev_version(REls, PrevVersion)]. |
448 |
|
|
449 |
|
-spec bcast_aff_messages(From :: jid:jid(), OldAffUsers :: aff_users(), |
450 |
|
NewAffUsers :: aff_users(), Attrs :: [{binary(), binary()}], |
451 |
|
VersionEl :: exml:element(), Children :: [jlib:xmlch()], |
452 |
|
HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok. |
453 |
|
bcast_aff_messages(_, [], [], _, _, _, _) -> |
454 |
525 |
ok; |
455 |
|
bcast_aff_messages(From, [{User, _} | ROldAffUsers], [], Attrs, VersionEl, Children, HandleFun) -> |
456 |
209 |
msg_to_leaving_user(From, User, Attrs, HandleFun), |
457 |
209 |
bcast_aff_messages(From, ROldAffUsers, [], Attrs, VersionEl, Children, HandleFun); |
458 |
|
bcast_aff_messages(From, [{{ToU, ToS} = User, _} | ROldAffUsers], [{User, _} | RNewAffUsers], |
459 |
|
Attrs, VersionEl, Children, HandleFun) -> |
460 |
245 |
msg_to_aff_user(From, ToU, ToS, Attrs, Children, HandleFun), |
461 |
245 |
bcast_aff_messages(From, ROldAffUsers, RNewAffUsers, Attrs, VersionEl, Children, HandleFun); |
462 |
|
bcast_aff_messages(From, [{User1, _} | ROldAffUsers], [{User2, _} | _] = NewAffUsers, |
463 |
|
Attrs, VersionEl, Children, HandleFun) when User1 < User2 -> |
464 |
91 |
msg_to_leaving_user(From, User1, Attrs, HandleFun), |
465 |
91 |
bcast_aff_messages(From, ROldAffUsers, NewAffUsers, Attrs, VersionEl, Children, HandleFun); |
466 |
|
bcast_aff_messages(From, OldAffUsers, [{{ToU, ToS}, _} = AffUser | RNewAffUsers], |
467 |
|
Attrs, VersionEl, Children, HandleFun) -> |
468 |
421 |
NotifForNewcomer = msg_envelope(?NS_MUC_LIGHT_AFFILIATIONS, |
469 |
|
[ VersionEl, aff_user_to_el(AffUser) ]), |
470 |
421 |
msg_to_aff_user(From, ToU, ToS, Attrs, NotifForNewcomer, HandleFun), |
471 |
421 |
bcast_aff_messages(From, OldAffUsers, RNewAffUsers, Attrs, VersionEl, Children, HandleFun). |
472 |
|
|
473 |
|
-spec msg_to_leaving_user(From :: jid:jid(), User :: jid:simple_bare_jid(), |
474 |
|
Attrs :: [{binary(), binary()}], |
475 |
|
HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok. |
476 |
|
msg_to_leaving_user(From, {ToU, ToS} = User, Attrs, HandleFun) -> |
477 |
300 |
NotifForLeaving = msg_envelope(?NS_MUC_LIGHT_AFFILIATIONS, [ aff_user_to_el({User, none}) ]), |
478 |
300 |
msg_to_aff_user(From, ToU, ToS, Attrs, NotifForLeaving, HandleFun). |
479 |
|
|
480 |
|
-spec msg_to_aff_user(From :: jid:jid(), ToU :: jid:luser(), ToS :: jid:lserver(), |
481 |
|
Attrs :: [{binary(), binary()}], Children :: [jlib:xmlch()], |
482 |
|
HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok. |
483 |
|
msg_to_aff_user(From, ToU, ToS, Attrs, Children, HandleFun) -> |
484 |
2043 |
To = jid:make_noprep(ToU, ToS, <<>>), |
485 |
2043 |
ToBin = jid:to_binary({ToU, ToS, <<>>}), |
486 |
2043 |
Packet = #xmlel{ name = <<"message">>, attrs = [{<<"to">>, ToBin} | Attrs], |
487 |
|
children = Children }, |
488 |
2043 |
HandleFun(From, To, Packet). |
489 |
|
|
490 |
|
-spec jids_from_room_with_resource(jid:jid(), binary()) -> |
491 |
|
{jid:jid(), binary()}. |
492 |
|
jids_from_room_with_resource(RoomJID, Resource) -> |
493 |
1020 |
From = jid:replace_resource(RoomJID, Resource), |
494 |
1020 |
FromBin = jid:to_binary(jid:to_lower(From)), |
495 |
1020 |
{From, FromBin}. |
496 |
|
|
497 |
|
-spec make_iq_result(FromBin :: binary(), ToBin :: binary(), ID :: binary(), |
498 |
|
XMLNS :: binary(), Els :: [jlib:xmlch()] | undefined) -> exml:element(). |
499 |
|
make_iq_result(FromBin, ToBin, ID, XMLNS, Els) -> |
500 |
604 |
Attrs = [ |
501 |
|
{<<"from">>, FromBin}, |
502 |
|
{<<"to">>, ToBin}, |
503 |
|
{<<"id">>, ID}, |
504 |
|
{<<"type">>, <<"result">>} |
505 |
|
], |
506 |
604 |
Query = make_query_el(XMLNS, Els), |
507 |
604 |
#xmlel{ name = <<"iq">>, attrs = Attrs, children = Query }. |
508 |
|
|
509 |
|
-spec make_query_el(binary(), [jlib:xmlch()] | undefined) -> [exml:element()]. |
510 |
|
make_query_el(_, undefined) -> |
511 |
563 |
[]; |
512 |
|
make_query_el(XMLNS, Els) -> |
513 |
41 |
[#xmlel{ name = <<"query">>, attrs = [{<<"xmlns">>, XMLNS}], children = Els }]. |
514 |
|
|
515 |
|
%%==================================================================== |
516 |
|
%% Common helpers and internal functions |
517 |
|
%%==================================================================== |
518 |
|
|
519 |
|
-spec safe_get_children(exml:element() | term()) -> [exml:element() | exml:cdata()]. |
520 |
332 |
safe_get_children(#xmlel{ children = Ch }) -> Ch; |
521 |
:-( |
safe_get_children(_) -> []. |
522 |
|
|
523 |
|
-spec b2action(ActionBin :: binary()) -> atom(). |
524 |
8 |
b2action(<<"allow">>) -> allow; |
525 |
14 |
b2action(<<"deny">>) -> deny. |
526 |
|
|
527 |
|
-spec action2b(Action :: atom()) -> binary(). |
528 |
:-( |
action2b(allow) -> <<"allow">>; |
529 |
2 |
action2b(deny) -> <<"deny">>. |
530 |
|
|
531 |
|
-spec b2what(WhatBin :: binary()) -> atom(). |
532 |
17 |
b2what(<<"user">>) -> user; |
533 |
5 |
b2what(<<"room">>) -> room. |
534 |
|
|
535 |
|
-spec what2b(What :: atom()) -> binary(). |
536 |
1 |
what2b(user) -> <<"user">>; |
537 |
1 |
what2b(room) -> <<"room">>. |
538 |
|
|
539 |
|
-spec room_event(mongoose_acc:t(), jid:jid()) -> mod_muc:room_event_data(). |
540 |
|
room_event(Acc, RoomJID) -> |
541 |
537 |
TS = mongoose_acc:timestamp(Acc), |
542 |
537 |
#{from_nick => <<>>, |
543 |
|
from_jid => RoomJID, |
544 |
|
room_jid => RoomJID, |
545 |
|
affiliation => owner, |
546 |
|
role => moderator, |
547 |
|
timestamp => TS}. |