./ct_report/coverage/mod_muc_light_codec_legacy.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_muc_light_codec_legacy.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : MUC Light codec for XEP-0045 compatibility
5 %%% Created : 27 Oct 2015 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
6 %%%----------------------------------------------------------------------
7
8 -module(mod_muc_light_codec_legacy).
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 %%====================================================================
21 %% API
22 %%====================================================================
23
24 -spec decode(From :: jid:jid(), To :: jid:jid(),
25 Stanza :: jlib:iq() | exml:element(),
26 Acc :: mongoose_acc:t()) -> mod_muc_light_codec_backend:decode_result().
27 decode(_From, #jid{ luser = ToU } = _To, #xmlel{ name = <<"presence">> } = Stanza, _Acc)
28 when ToU =/= <<>> ->
29
:-(
case {exml_query:path(Stanza, [{element, <<"x">>}, {attr, <<"xmlns">>}]),
30 exml_query:attr(Stanza, <<"type">>)} of
31 {?NS_MUC, Available} when Available =:= undefined orelse
32
:-(
Available =:= <<"available">> -> {ok, {set, #create{}}};
33
:-(
_ -> ignore
34 end;
35 decode(_From, #jid{ lresource = Resource }, _Stanza, _Acc) when Resource =/= <<>> ->
36
:-(
{error, bad_request};
37 decode(_From, _To, #xmlel{ name = <<"message">> } = Stanza, _Acc) ->
38
:-(
decode_message(Stanza);
39 decode(From, _To, #xmlel{ name = <<"iq">> } = Stanza, _Acc) ->
40
:-(
decode_iq(From, jlib:iq_query_info(Stanza));
41 decode(From, _To, #iq{} = IQ, _Acc) ->
42
:-(
decode_iq(From, IQ);
43 decode(_, _, _, _Acc) ->
44
:-(
{error, bad_request}.
45
46 -spec encode(Request :: muc_light_encode_request(),
47 OriginalSender :: jid:jid(), RoomUS :: jid:jid(),
48 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler(),
49 Acc :: mongoose_acc:t()) -> mongoose_acc:t().
50 encode({#msg{} = Msg, AffUsers}, Sender, RoomBareJid, HandleFun, Acc) ->
51
:-(
US = jid:to_lus(Sender),
52
:-(
Aff = get_sender_aff(AffUsers, US),
53
:-(
FromNick = jid:to_bare_binary(Sender),
54
:-(
{RoomJID, RoomBin} = jids_from_room_with_resource(RoomBareJid, FromNick),
55
:-(
Attrs = [
56 {<<"id">>, Msg#msg.id},
57 {<<"type">>, <<"groupchat">>},
58 {<<"from">>, RoomBin}
59 ],
60
:-(
MsgForArch = #xmlel{ name = <<"message">>, attrs = Attrs, children = Msg#msg.children },
61
:-(
TS = mongoose_acc:timestamp(Acc),
62
:-(
EventData = #{from_nick => FromNick,
63 from_jid => Sender,
64 room_jid => RoomBareJid,
65 affiliation => Aff,
66 role => mod_muc_light_utils:light_aff_to_muc_role(Aff),
67 timestamp => TS},
68
:-(
HostType = mod_muc_light_utils:acc_to_host_type(Acc),
69
:-(
Packet1 = #xmlel{ children = Children }
70 = mongoose_hooks:filter_room_packet(HostType, MsgForArch, EventData),
71
:-(
lists:foreach(
72 fun({{U, S}, _}) ->
73
:-(
send_to_aff_user(RoomJID, U, S, <<"message">>, Attrs, Children, HandleFun)
74 end, AffUsers),
75
:-(
mongoose_acc:update_stanza(#{from_jid => RoomJID,
76 to_jid => RoomBareJid,
77 element => Packet1}, Acc);
78 encode(OtherCase, Sender, RoomBareJid, HandleFun, Acc) ->
79
:-(
{RoomJID, RoomBin} = jids_from_room_with_resource(RoomBareJid, <<>>),
80
:-(
case encode_meta(OtherCase, RoomJID, Sender, HandleFun, Acc) of
81 {iq_reply, ID} ->
82
:-(
IQRes = make_iq_result(RoomBin, jid:to_binary(Sender), ID, <<>>, undefined),
83
:-(
HandleFun(RoomJID, Sender, IQRes);
84 {iq_reply, XMLNS, Els, ID} ->
85
:-(
IQRes = make_iq_result(RoomBin, jid:to_binary(Sender), ID, XMLNS, Els),
86
:-(
HandleFun(RoomJID, Sender, IQRes);
87 noreply ->
88
:-(
Acc
89 end.
90
91 -spec encode_error(
92 ErrMsg :: tuple(), OrigFrom :: jid:jid(), OrigTo :: jid:jid(),
93 OrigPacket :: exml:element(), Acc :: mongoose_acc:t()) ->
94 mongoose_acc:t().
95 encode_error(_, OrigFrom, OrigTo, #xmlel{ name = <<"presence">> } = OrigPacket, Acc) ->
96 %% The only error case for valid presence is registration-required for room creation
97
:-(
X = #xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}] },
98
:-(
mod_muc_light_codec_backend:encode_error({error, registration_required}, [X], OrigFrom, OrigTo,
99 OrigPacket, Acc);
100 encode_error(ErrMsg, OrigFrom, OrigTo, OrigPacket, Acc) ->
101
:-(
mod_muc_light_codec_backend:encode_error(ErrMsg, [], OrigFrom, OrigTo, OrigPacket, Acc).
102
103 %%====================================================================
104 %% Message decoding
105 %%====================================================================
106
107 -spec decode_message(Packet :: exml:element()) ->
108 {ok, muc_light_packet()} | {error, bad_request} | ignore.
109 decode_message(#xmlel{ attrs = Attrs, children = Children }) ->
110
:-(
decode_message_by_type(lists:keyfind(<<"type">>, 1, Attrs),
111 lists:keyfind(<<"id">>, 1, Attrs), Children).
112
113 -spec decode_message_by_type(Type :: {binary(), binary()} | false,
114 Id :: {binary(), binary()} | false,
115 Children :: [jlib:xmlch()]) ->
116 {ok, msg() | {set, mod_muc_light_room_config:kv()}} | {error, bad_request} | ignore.
117 decode_message_by_type({_, <<"groupchat">>}, _, [#xmlel{ name = <<"subject">> } = SubjectEl]) ->
118
:-(
{ok, {set, #config{ raw_config = [{<<"subject">>, exml_query:cdata(SubjectEl)}] }}};
119 decode_message_by_type({_, <<"groupchat">>}, Id, Children) ->
120
:-(
{ok, #msg{ children = Children, id = ensure_id(Id) }};
121 decode_message_by_type({_, <<"error">>}, _, _) ->
122
:-(
ignore;
123 decode_message_by_type(_, _, _) ->
124
:-(
{error, bad_request}.
125
126 -spec ensure_id(Id :: {binary(), binary()} | false) -> binary().
127
:-(
ensure_id(false) -> mongoose_bin:gen_from_timestamp();
128
:-(
ensure_id({_, Id}) -> Id.
129
130 %%====================================================================
131 %% IQ decoding
132 %%====================================================================
133
134 -spec decode_iq(From :: jid:jid(), IQ :: jlib:iq()) ->
135 {ok, muc_light_packet() | muc_light_disco() | jlib:iq()} | {error, bad_request} | ignore.
136 decode_iq(_From, #iq{ xmlns = ?NS_MUC_OWNER, type = get, sub_el = _QueryEl, id = ID }) ->
137
:-(
{ok, {get, #config{ id = ID }}};
138 decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_OWNER, type = set, sub_el = QueryEl, id = ID }) ->
139
:-(
case exml_query:subelement(QueryEl, <<"destroy">>) of
140 undefined ->
141
:-(
case parse_config_form(QueryEl) of
142 {ok, RawConfig} ->
143
:-(
{ok, {set, #config{ id = ID, raw_config = RawConfig }}};
144 {error, Reason} ->
145
:-(
?LOG_WARNING(#{what => muc_parse_config_failed,
146
:-(
from_jid => jid:to_binary(From), iq => IQ, reason => Reason}),
147
:-(
{error, bad_request}
148 end;
149 _ ->
150
:-(
{ok, {set, #destroy{ id = ID }}}
151 end;
152 decode_iq(_From, #iq{ xmlns = ?NS_MUC_ADMIN, type = get, sub_el = _QueryEl, id = ID }) ->
153
:-(
{ok, {get, #affiliations{ id = ID }}};
154 decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_ADMIN, type = set, sub_el = QueryEl, id = ID }) ->
155
:-(
try parse_aff_users(exml_query:subelements(QueryEl, <<"item">>)) of
156 {ok, AffUsers} ->
157
:-(
{ok, {set, #affiliations{ id = ID, aff_users = AffUsers }}}
158 catch Class:Reason:Stacktrace ->
159
:-(
?LOG_WARNING(#{what => muc_parse_aff_users_failed,
160 from_jid => jid:to_binary(From), iq => IQ,
161
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
162
:-(
{error, bad_request}
163 end;
164 decode_iq(_From, #iq{ xmlns = ?NS_PRIVACY, type = get, id = ID }) ->
165
:-(
{ok, {get, #blocking{ id = ID }}};
166 decode_iq(From, IQ = #iq{ xmlns = ?NS_PRIVACY, type = set,
167 sub_el = #xmlel{ children = Lists }, id = ID }) ->
168
:-(
case lists:keyfind([{<<"name">>, ?NS_MUC_LIGHT}], #xmlel.attrs, Lists) of
169 false ->
170
:-(
ignore;
171 List ->
172
:-(
try parse_blocking_list(exml_query:subelements(List, <<"item">>)) of
173 {ok, BlockingList} ->
174
:-(
{ok, {set, #blocking{ id = ID, items = BlockingList }}}
175 catch Class:Reason:Stacktrace ->
176
:-(
?LOG_WARNING(#{what => muc_parse_blocking_list_failed,
177 from_jid => jid:to_binary(From), iq => IQ,
178
:-(
class => Class, reason => Reason, stacktrace => Stacktrace})
179 end
180 end;
181 decode_iq(_From, #iq{ xmlns = ?NS_DISCO_ITEMS, type = get, id = ID} = IQ) ->
182
:-(
{ok, {get, #disco_items{ id = ID, rsm = jlib:rsm_decode(IQ) }}};
183 decode_iq(_From, #iq{ xmlns = ?NS_DISCO_INFO, type = get, id = ID}) ->
184
:-(
{ok, {get, #disco_info{ id = ID }}};
185 decode_iq(_From, #iq{ type = error }) ->
186
:-(
ignore;
187 decode_iq(_From, #iq{} = IQ) ->
188
:-(
{ok, IQ}.
189
190 %% ------------------ Parsers ------------------
191
192 -spec parse_config_form(exml:element()) -> {ok, [{binary(), binary()}]} | {error, binary()}.
193 parse_config_form(QueryEl) ->
194
:-(
case mongoose_data_forms:find_and_parse_form(QueryEl) of
195 #{type := <<"submit">>, kvs := KVs} ->
196
:-(
{ok, [{K, V} || {K, [V]} <- maps:to_list(KVs)]};
197 #{} ->
198
:-(
{error, <<"Invalid form type">>};
199 {error, Msg} ->
200
:-(
{error, Msg}
201 end.
202
203 -spec parse_aff_users(Els :: [jlib:xmlch()]) -> {ok, aff_users()}.
204 parse_aff_users(Els) ->
205
:-(
parse_aff_users(Els, []).
206
207 -spec parse_aff_users(Els :: [jlib:xmlch()], AffUsersAcc :: aff_users()) -> {ok, aff_users()}.
208 parse_aff_users([], AffUsersAcc) ->
209
:-(
{ok, AffUsersAcc};
210 parse_aff_users([Item | RItemsEls], AffUsersAcc) ->
211
:-(
AffBin = exml_query:attr(Item, <<"affiliation">>),
212
:-(
JIDBin = exml_query:attr(Item, <<"jid">>),
213
:-(
#jid{} = JID = jid:from_binary(JIDBin),
214
:-(
Aff = mod_muc_light_utils:b2aff(AffBin),
215
:-(
parse_aff_users(RItemsEls, [{jid:to_lus(JID), Aff} | AffUsersAcc]).
216
217 -spec parse_blocking_list(Els :: [jlib:xmlch()]) -> {ok, [blocking_item()]}.
218 parse_blocking_list(ItemsEls) ->
219
:-(
parse_blocking_list(ItemsEls, []).
220
221 -spec parse_blocking_list(Els :: [jlib:xmlch()], ItemsAcc :: [blocking_item()]) ->
222 {ok, [blocking_item()]}.
223 parse_blocking_list([], ItemsAcc) ->
224
:-(
{ok, ItemsAcc};
225 parse_blocking_list([Item | RItemsEls], ItemsAcc) ->
226
:-(
JIDBin = exml_query:attr(Item, <<"value">>),
227
:-(
ActionBin = exml_query:attr(Item, <<"action">>),
228
:-(
#jid{} = JID = jid:from_binary(JIDBin),
229
:-(
Action = b2action(ActionBin),
230
:-(
{What, Who} = case {JID#jid.luser =:= <<>>, JID#jid.lresource =:= <<>>} of
231 {false, true} ->
232
:-(
{room, JID};
233 {true, false} ->
234
:-(
{user, jid:from_binary(JID#jid.lresource)}
235 end,
236
:-(
parse_blocking_list(RItemsEls, [{What, Action, jid:to_lus(Who)} | ItemsAcc]).
237
238 %%====================================================================
239 %% Encoding
240 %%====================================================================
241
242 -spec encode_meta(Request :: muc_light_encode_request(), RoomJID :: jid:jid(),
243 SenderJID :: jid:jid(),
244 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler(),
245 Acc :: mongoose_acc:t()) ->
246 {iq_reply, ID :: binary()} |
247 {iq_reply, XMLNS :: binary(), Els :: [jlib:xmlch()], ID :: binary()} |
248 noreply.
249 encode_meta({get, #disco_info{ id = ID }}, RoomJID, SenderJID, _HandleFun, Acc) ->
250
:-(
HostType = mod_muc_light_utils:acc_to_host_type(Acc),
251
:-(
IdentityXML = mongoose_disco:identities_to_xml([identity()]),
252
:-(
FeatureXML = mongoose_disco:get_muc_features(HostType, SenderJID, RoomJID, <<>>, <<>>,
253 [?NS_MUC]),
254
:-(
DiscoEls = IdentityXML ++ FeatureXML,
255
:-(
{iq_reply, ?NS_DISCO_INFO, DiscoEls, ID};
256 encode_meta({get, #disco_items{ rooms = Rooms, id = ID, rsm = RSMOut }},
257 _RoomJID, _SenderJID, _HandleFun, _Acc) ->
258
:-(
DiscoEls = [ #xmlel{ name = <<"item">>,
259 attrs = [{<<"jid">>, <<RoomU/binary, $@, RoomS/binary>>},
260 {<<"name">>, RoomName}] }
261
:-(
|| {{RoomU, RoomS}, RoomName, _RoomVersion} <- Rooms ],
262
:-(
{iq_reply, ?NS_DISCO_ITEMS, jlib:rsm_encode(RSMOut) ++ DiscoEls, ID};
263 encode_meta({get, #config{} = Config}, _RoomJID, _SenderJID, _HandleFun, _Acc) ->
264
:-(
Fields = [#{var => K, type => <<"text-single">>, values => [V]}
265
:-(
|| {K, V} <- Config#config.raw_config],
266
:-(
XEl = mongoose_data_forms:form(#{title => <<"Configuration form for the room">>,
267 ns => <<"http://jabber.org/protocol/muc#roomconfig">>,
268 fields => Fields}),
269
:-(
{iq_reply, ?NS_MUC_OWNER, [XEl], Config#config.id};
270 encode_meta({get, #affiliations{} = Affs}, _RoomJID, _SenderJID, _HandleFun, _Acc) ->
271
:-(
AffEls = [ aff_user_to_item(AffUser) || AffUser <- Affs#affiliations.aff_users ],
272
:-(
{iq_reply, ?NS_MUC_ADMIN, AffEls, Affs#affiliations.id};
273 encode_meta({set, #affiliations{} = Affs, OldAffUsers, NewAffUsers},
274 RoomJID, SenderJID, HandleFun, _Acc) ->
275
:-(
bcast_aff_messages(RoomJID, OldAffUsers, NewAffUsers, SenderJID,
276 Affs#affiliations.aff_users, HandleFun),
277
:-(
{iq_reply, Affs#affiliations.id};
278 encode_meta({get, #blocking{} = Blocking}, RoomJID, _SenderJID, _HandleFun, Acc) ->
279
:-(
HostType = mod_muc_light_utils:acc_to_host_type(Acc),
280
:-(
ServerHost = mod_muc_light_utils:room_jid_to_server_host(RoomJID),
281
:-(
MUCHost = mongoose_subdomain_utils:get_fqdn(mod_muc_light:subdomain_pattern(HostType), ServerHost),
282
:-(
BlockingEls = [ blocking_to_el(BlockingItem, MUCHost)
283
:-(
|| BlockingItem <- Blocking#blocking.items ],
284
:-(
Blocklist = #xmlel{ name = <<"list">>, attrs = [{<<"name">>, ?NS_MUC_LIGHT}],
285 children = BlockingEls },
286
:-(
{iq_reply, ?NS_PRIVACY, [Blocklist], Blocking#blocking.id};
287 encode_meta({set, #blocking{ id = ID }}, _RoomJID, _SenderJID, _HandleFun, _Acc) ->
288
:-(
{iq_reply, ID};
289 encode_meta({set, #create{} = Create, _UniqueRequested}, RoomJID, _SenderJID, HandleFun, _Acc) ->
290
:-(
[{{ToU, ToS}, CreatorAff}] = Create#create.aff_users,
291
:-(
ToBin = jid:to_binary({ToU, ToS, <<>>}),
292
:-(
{From, FromBin} = jids_from_room_with_resource(RoomJID, ToBin),
293
:-(
Attrs = [{<<"from">>, FromBin}],
294
:-(
{AffBin, RoleBin} = case CreatorAff of
295
:-(
owner -> {<<"owner">>, <<"moderator">>};
296
:-(
member -> {<<"member">>, <<"participant">>}
297 end,
298
:-(
NotifEls = [ #xmlel{ name = <<"item">>,
299 attrs = [{<<"affiliation">>, AffBin}, {<<"role">>, RoleBin}]},
300 status(<<"110">>), status(<<"201">>) ],
301
:-(
Children = envelope(?NS_MUC_USER, NotifEls),
302
303
:-(
send_to_aff_user(From, ToU, ToS, <<"presence">>, Attrs, Children, HandleFun),
304
:-(
noreply;
305 encode_meta({set, #destroy{ id = ID }, AffUsers}, RoomJID, _SenderJID, HandleFun, _Acc) ->
306
:-(
lists:foreach(
307 fun({{U, S}, _}) ->
308
:-(
FromJID = jid:replace_resource(RoomJID, jid:to_binary({U, S, <<>>})),
309
:-(
Attrs = [{<<"from">>, jid:to_binary(FromJID)},
310 {<<"type">>, <<"unavailable">>}],
311
:-(
Children = [ #xmlel{ name = <<"item">>,
312 attrs = [{<<"affiliation">>, <<"none">>},
313 {<<"role">>, <<"none">>}] },
314 #xmlel{ name = <<"destroy">> } ],
315
:-(
send_to_aff_user(FromJID, U, S, <<"presence">>, Attrs,
316 envelope(?NS_MUC_USER, Children), HandleFun)
317 end, AffUsers),
318
319
:-(
{iq_reply, ID};
320 encode_meta({set, #config{ raw_config = [{<<"subject">>, Subject}], id = ID }, AffUsers},
321 RoomJID, _SenderJID, HandleFun, _Acc) ->
322
:-(
Attrs = [
323 {<<"id">>, ID},
324 {<<"type">>, <<"groupchat">>},
325 {<<"from">>, jid:to_binary(RoomJID)}
326 ],
327
:-(
SubjectEl = #xmlel{ name = <<"subject">>, children = [ #xmlcdata{ content = Subject } ] },
328
:-(
lists:foreach(
329 fun({{U, S}, _}) ->
330
:-(
send_to_aff_user(RoomJID, U, S, <<"message">>, Attrs, [SubjectEl], HandleFun)
331 end, AffUsers),
332
:-(
noreply;
333 encode_meta({set, #config{} = Config, AffUsers}, RoomJID, _SenderJID, HandleFun, _Acc) ->
334
:-(
Attrs = [{<<"id">>, Config#config.id},
335 {<<"from">>, jid:to_binary(RoomJID)},
336 {<<"type">>, <<"groupchat">>}],
337
:-(
ConfigNotif = envelope(?NS_MUC_USER, [status(<<"104">>)]),
338
:-(
lists:foreach(
339 fun({{U, S}, _}) ->
340
:-(
send_to_aff_user(RoomJID, U, S, <<"message">>, Attrs, ConfigNotif, HandleFun)
341 end, AffUsers),
342
343
:-(
{iq_reply, Config#config.id}.
344
345 %% --------------------------- Helpers ---------------------------
346
347 -spec identity() -> mongoose_disco:identity().
348 identity() ->
349
:-(
#{category => <<"conference">>, type => <<"text">>, name => <<"MUC Light (legacy)">>}.
350
351 -spec aff_user_to_item(aff_user()) -> exml:element().
352 aff_user_to_item({User, Aff}) ->
353
:-(
UserBin = jid:to_binary(User),
354
:-(
{RoleBin, NickEl} = case Aff of
355
:-(
owner -> {<<"moderator">>, [{<<"nick">>, UserBin}]};
356
:-(
member -> {<<"participant">>, [{<<"nick">>, UserBin}]};
357
:-(
none -> {<<"none">>, []}
358 end,
359
:-(
#xmlel{ name = <<"item">>,
360 attrs = [{<<"affiliation">>, mod_muc_light_utils:aff2b(Aff)},
361 {<<"jid">>, UserBin},
362 {<<"role">>, RoleBin} | NickEl] }.
363
364 -spec blocking_to_el(BlockingItem :: blocking_item(), Service :: binary()) -> exml:element().
365 blocking_to_el({What, Action, {WhoU, WhoS}}, Service) ->
366
:-(
WhoBin = jid:to_binary({WhoU, WhoS, <<>>}),
367
:-(
Value = case What of
368
:-(
room -> WhoBin;
369
:-(
user -> <<Service/binary, $/, WhoBin/binary>>
370 end,
371
:-(
#xmlel{ name = <<"item">>,
372 attrs = [
373 {<<"type">>, <<"jid">>},
374 {<<"value">>, Value},
375 {<<"action">>, action2b(Action)},
376 {<<"order">>, <<"1">>}
377 ] }.
378
379 -spec envelope(XMLNS :: binary(), Children :: [jlib:xmlch()]) -> [jlib:xmlch()].
380 envelope(XMLNS, Children) ->
381
:-(
[ #xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, XMLNS}], children = Children } ].
382
383 -spec bcast_aff_messages(Room :: jid:jid(), OldAffUsers :: aff_users(),
384 NewAffUsers :: aff_users(), SenderJID :: jid:jid(),
385 ChangedAffUsers :: aff_users(),
386 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok.
387 bcast_aff_messages(_, [], [], _, _, _) ->
388
:-(
ok;
389 bcast_aff_messages(Room, [{User, _} | ROldAffUsers], [], SenderJID, ChangedAffUsers, HandleFun) ->
390
:-(
msg_to_leaving_user(Room, User, HandleFun),
391
:-(
bcast_aff_messages(Room, ROldAffUsers, [], SenderJID, ChangedAffUsers, HandleFun);
392 bcast_aff_messages(Room, [{{ToU, ToS} = User, _} | ROldAffUsers], [{User, _} | RNewAffUsers],
393 SenderJID, ChangedAffUsers, HandleFun) ->
394
:-(
lists:foreach(
395 fun({{ChangedU, ChangedS}, NewAff} = ChangedAffUser) ->
396
:-(
ChangedUserBin = jid:to_binary({ChangedU, ChangedS, <<>>}),
397
:-(
{From, FromBin} = jids_from_room_with_resource(Room,
398 ChangedUserBin),
399
:-(
Attrs0 = [{<<"from">>, FromBin}],
400
:-(
ElToEnvelope0 = aff_user_to_item(ChangedAffUser),
401
:-(
{Attrs, ElsToEnvelope} = case NewAff of
402
:-(
none -> {[{<<"type">>, <<"unavailable">>} | Attrs0],
403 [ElToEnvelope0, status(<<"321">>)]};
404
:-(
_ -> {Attrs0, [ElToEnvelope0]}
405 end,
406
:-(
Children = envelope(?NS_MUC_USER, ElsToEnvelope),
407
:-(
send_to_aff_user(From, ToU, ToS, <<"presence">>, Attrs, Children, HandleFun)
408 end, ChangedAffUsers),
409
:-(
bcast_aff_messages(Room, ROldAffUsers, RNewAffUsers, SenderJID, ChangedAffUsers, HandleFun);
410 bcast_aff_messages(Room, [{User1, _} | ROldAffUsers], [{User2, _} | _] = NewAffUsers,
411 SenderJID, ChangedAffUsers, HandleFun) when User1 < User2 ->
412
:-(
msg_to_leaving_user(Room, User1, HandleFun),
413
:-(
bcast_aff_messages(Room, ROldAffUsers, NewAffUsers, SenderJID, ChangedAffUsers, HandleFun);
414 bcast_aff_messages(Room, OldAffUsers, [{{ToU, ToS}, _} | RNewAffUsers],
415 SenderJID, ChangedAffUsers, HandleFun) ->
416
:-(
InviterBin = jid:to_binary({SenderJID#jid.luser, SenderJID#jid.lserver, <<>>}),
417
:-(
RoomBin = jid:to_binary(jid:to_lower(Room)),
418
:-(
InviteEl = #xmlel{ name = <<"invite">>,
419 attrs = [{<<"from">>, InviterBin}] },
420
:-(
NotifForNewcomer = envelope(?NS_MUC_USER, [InviteEl]),
421
:-(
send_to_aff_user(Room, ToU, ToS, <<"message">>, [{<<"from">>, RoomBin}],
422 NotifForNewcomer, HandleFun),
423
:-(
bcast_aff_messages(Room, OldAffUsers, RNewAffUsers, SenderJID, ChangedAffUsers, HandleFun).
424
425 -spec msg_to_leaving_user(Room :: jid:jid(), User :: jid:simple_bare_jid(),
426 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok.
427 msg_to_leaving_user(Room, {ToU, ToS} = User, HandleFun) ->
428
:-(
UserBin = jid:to_binary({ToU, ToS, <<>>}),
429
:-(
{From, FromBin} = jids_from_room_with_resource(Room, UserBin),
430
:-(
Attrs = [{<<"from">>, FromBin},
431 {<<"type">>, <<"unavailable">>}],
432
:-(
NotifForLeaving = envelope(?NS_MUC_USER, [ aff_user_to_item({User, none}), status(<<"321">>) ]),
433
:-(
send_to_aff_user(From, ToU, ToS, <<"presence">>, Attrs, NotifForLeaving, HandleFun).
434
435 -spec send_to_aff_user(From :: jid:jid(), ToU :: jid:luser(), ToS :: jid:lserver(),
436 Name :: binary(), Attrs :: [{binary(), binary()}],
437 Children :: [jlib:xmlch()],
438 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok.
439 send_to_aff_user(From, ToU, ToS, Name, Attrs, Children, HandleFun) ->
440
:-(
To = jid:make_noprep(ToU, ToS, <<>>),
441
:-(
ToBin = jid:to_binary({ToU, ToS, <<>>}),
442
:-(
Packet = #xmlel{ name = Name, attrs = [{<<"to">>, ToBin} | Attrs],
443 children = Children },
444
:-(
HandleFun(From, To, Packet).
445
446 -spec jids_from_room_with_resource(Room :: jid:jid(), binary()) ->
447 {jid:jid(), binary()}.
448 jids_from_room_with_resource(RoomJID, Resource) ->
449
:-(
From = jid:replace_resource(RoomJID, Resource),
450
:-(
FromBin = jid:to_binary(jid:to_lower(From)),
451
:-(
{From, FromBin}.
452
453 -spec make_iq_result(FromBin :: binary(), ToBin :: binary(), ID :: binary(),
454 XMLNS :: binary(), Els :: [jlib:xmlch()] | undefined) -> exml:element().
455 make_iq_result(FromBin, ToBin, ID, XMLNS, Els) ->
456
:-(
Attrs = [
457 {<<"from">>, FromBin},
458 {<<"to">>, ToBin},
459 {<<"id">>, ID},
460 {<<"type">>, <<"result">>}
461 ],
462
:-(
Query = make_query_el(XMLNS, Els),
463
:-(
#xmlel{ name = <<"iq">>, attrs = Attrs, children = Query }.
464
465 -spec make_query_el(binary(), [jlib:xmlch()] | undefined) -> [exml:element()].
466 make_query_el(_, undefined) ->
467
:-(
[];
468 make_query_el(XMLNS, Els) ->
469
:-(
[#xmlel{ name = <<"query">>, attrs = [{<<"xmlns">>, XMLNS}], children = Els }].
470
471 -spec status(Code :: binary()) -> exml:element().
472
:-(
status(Code) -> #xmlel{ name = <<"status">>, attrs = [{<<"code">>, Code}] }.
473
474 %%====================================================================
475 %% Common helpers and internal functions
476 %%====================================================================
477
478 -spec b2action(ActionBin :: binary()) -> atom().
479
:-(
b2action(<<"allow">>) -> allow;
480
:-(
b2action(<<"deny">>) -> deny.
481
482 -spec action2b(Action :: atom()) -> binary().
483
:-(
action2b(allow) -> <<"allow">>;
484
:-(
action2b(deny) -> <<"deny">>.
485
486 get_sender_aff(Users, US) ->
487
:-(
case lists:keyfind(US, 1, Users) of
488
:-(
{US, Aff} -> Aff;
489
:-(
_ -> undefined
490 end.
Line Hits Source