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