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. |