1 |
|
%% @doc Provide an interface for frontends (like graphql or ctl) to manage MUC rooms. |
2 |
|
-module(mod_muc_api). |
3 |
|
|
4 |
|
-export([get_rooms/4, |
5 |
|
get_room_config/1, |
6 |
|
get_room_config/2, |
7 |
|
get_room_messages/3, |
8 |
|
get_room_messages/4, |
9 |
|
modify_room_config/2, |
10 |
|
modify_room_config/3, |
11 |
|
get_room_users/1, |
12 |
|
get_room_users/2, |
13 |
|
get_room_affiliations/2, |
14 |
|
get_room_affiliations/3, |
15 |
|
create_instant_room/3, |
16 |
|
invite_to_room/4, |
17 |
|
send_message_to_room/3, |
18 |
|
send_private_message/4, |
19 |
|
kick_user_from_room/3, |
20 |
|
kick_user_from_room/4, |
21 |
|
set_affiliation/3, |
22 |
|
set_affiliation/4, |
23 |
|
enter_room/3, |
24 |
|
exit_room/2, |
25 |
|
set_role/4, |
26 |
|
set_role/3, |
27 |
|
delete_room/2, |
28 |
|
delete_room/3]). |
29 |
|
|
30 |
|
-ignore_xref([get_rooms/4, get_rooms/5, delete_room/3, |
31 |
|
get_room_config/2, get_room_users/2]). |
32 |
|
|
33 |
|
-include("jlib.hrl"). |
34 |
|
-include("mongoose_rsm.hrl"). |
35 |
|
-include("mod_muc_room.hrl"). |
36 |
|
|
37 |
|
-type short_room_desc() :: #{jid := jid:jid(), |
38 |
|
title := binary(), |
39 |
|
private => boolean(), |
40 |
|
users_number => non_neg_integer()}. |
41 |
|
|
42 |
|
-type get_rooms_result() :: {[short_room_desc()], jlib:rsm_out()}. |
43 |
|
|
44 |
|
-type user_map() :: |
45 |
|
#{jid := jid:jid() | null, |
46 |
|
role := mod_muc:role(), |
47 |
|
nick := mod_muc:nick()}. |
48 |
|
|
49 |
|
-type room_conf_mod_fun() :: fun((mod_muc_room:config()) -> mod_muc_room:config()). |
50 |
|
|
51 |
|
-type aff_item() :: {jid:simple_jid(), mod_muc:affiliation()}. |
52 |
|
|
53 |
|
-export_type([user_map/0, short_room_desc/0]). |
54 |
|
|
55 |
|
-define(ROOM_DELETED_SUCC_RESULT, {ok, "Room deleted successfully"}). |
56 |
|
-define(USER_CANNOT_ACCESS_ROOM_RESULT, |
57 |
|
{not_allowed, "Given user does not have permission to read this room"}). |
58 |
|
-define(ROOM_NOT_FOUND_RESULT, {room_not_found, "Room not found"}). |
59 |
|
-define(DELETE_NONEXISTENT_ROOM_RESULT, {room_not_found, "Cannot remove non-existent room"}). |
60 |
|
-define(USER_NOT_FOUND_RESULT, {user_not_found, "Given user not found"}). |
61 |
|
-define(MUC_SERVER_NOT_FOUND_RESULT, {muc_server_not_found, "MUC server not found"}). |
62 |
|
-define(MODERATOR_NOT_FOUND_RESULT, {moderator_not_found, "Moderator user not found"}). |
63 |
|
-define(MODERATOR_RES_NOT_FOUND_RESULT, |
64 |
|
{moderator_not_found, "Resource with moderator role not found"}). |
65 |
|
-define(SET_ROLE_SUCC_RESULT, {ok, "Role set successfully"}). |
66 |
|
|
67 |
|
-spec get_rooms(jid:lserver(), jid:jid(), non_neg_integer() | undefined, |
68 |
|
non_neg_integer()) -> {ok, get_rooms_result()} | {muc_server_not_found, iolist()}. |
69 |
|
get_rooms(MUCServer, From, Limit, Index) -> |
70 |
30 |
case mongoose_domain_api:get_subdomain_host_type(MUCServer) of |
71 |
|
{ok, _HostType} -> |
72 |
24 |
{Rooms, RSM} = mod_muc:get_vh_rooms(MUCServer, #rsm_in{max = Limit, index = Index}), |
73 |
24 |
Rooms2 = lists:filtermap(fun(R) -> room_to_item(R, MUCServer, From) end, Rooms), |
74 |
24 |
{ok, {Rooms2, RSM}}; |
75 |
|
{error, not_found} -> |
76 |
6 |
?MUC_SERVER_NOT_FOUND_RESULT |
77 |
|
end. |
78 |
|
|
79 |
|
-spec get_room_config(jid:jid(), jid:jid()) -> |
80 |
|
{ok, mod_muc_room:config()} | {not_allowed | room_not_found, iolist()}. |
81 |
|
get_room_config(RoomJID, UserJID) -> |
82 |
3 |
case mod_muc_room:is_room_owner(RoomJID, UserJID) of |
83 |
|
{ok, true} -> |
84 |
1 |
{ok, Config} = mod_muc_room:get_room_config(RoomJID), |
85 |
1 |
{ok, Config}; |
86 |
|
{ok, false} -> |
87 |
1 |
{not_allowed, "Given user does not have permission to read config"}; |
88 |
|
{error, not_found} -> |
89 |
1 |
?ROOM_NOT_FOUND_RESULT |
90 |
|
end. |
91 |
|
|
92 |
|
-spec get_room_config(jid:jid()) -> {ok, mod_muc_room:config()} | {room_not_found, iolist()}. |
93 |
|
get_room_config(RoomJID) -> |
94 |
15 |
case mod_muc_room:get_room_config(RoomJID) of |
95 |
|
{ok, Config} -> |
96 |
13 |
{ok, Config}; |
97 |
|
{error, not_found} -> |
98 |
2 |
?ROOM_NOT_FOUND_RESULT |
99 |
|
end. |
100 |
|
|
101 |
|
-spec create_instant_room(jid:jid(), jid:jid(), binary()) -> |
102 |
|
{ok, short_room_desc()} | {internal | user_not_found | room_not_found, iolist()}. |
103 |
|
create_instant_room(BareRoomJID = #jid{luser = Name, lresource = <<>>}, OwnerJID, Nick) -> |
104 |
|
%% Because these stanzas are sent on the owner's behalf through |
105 |
|
%% the HTTP API, they will certainly receive stanzas as a |
106 |
|
%% consequence, even if their client(s) did not initiate this. |
107 |
21 |
case ejabberd_auth:does_user_exist(OwnerJID) of |
108 |
|
true -> |
109 |
18 |
UserRoomJID = jid:replace_resource(BareRoomJID, Nick), |
110 |
|
%% Send presence to create a room. |
111 |
18 |
ejabberd_router:route(OwnerJID, UserRoomJID, |
112 |
|
presence(OwnerJID, UserRoomJID, undefined)), |
113 |
|
%% Send IQ set to unlock the room. |
114 |
18 |
ejabberd_router:route(OwnerJID, BareRoomJID, |
115 |
|
declination(OwnerJID, BareRoomJID)), |
116 |
18 |
case verify_room(BareRoomJID, OwnerJID) of |
117 |
|
ok -> |
118 |
14 |
{ok, #{jid => BareRoomJID, title => Name, private => false, users_number => 0}}; |
119 |
|
Error -> |
120 |
4 |
Error |
121 |
|
end; |
122 |
|
false -> |
123 |
3 |
?USER_NOT_FOUND_RESULT |
124 |
|
end. |
125 |
|
|
126 |
|
-spec modify_room_config(jid:jid(), jid:jid(), room_conf_mod_fun()) -> |
127 |
|
{ok, mod_muc_room:config()} | {not_allowed | room_not_found, iolist()}. |
128 |
|
modify_room_config(RoomJID, UserJID, Fun) -> |
129 |
3 |
case mod_muc_room:is_room_owner(RoomJID, UserJID) of |
130 |
|
{ok, true} -> |
131 |
1 |
{ok, modify_room_config_raw(RoomJID, Fun)}; |
132 |
|
{ok, false} -> |
133 |
1 |
{not_allowed, "Given user does not have permission to change the config"}; |
134 |
|
{error, not_found} -> |
135 |
1 |
?ROOM_NOT_FOUND_RESULT |
136 |
|
end. |
137 |
|
|
138 |
|
-spec modify_room_config(jid:jid(), room_conf_mod_fun()) -> |
139 |
|
{ok, mod_muc_room:config()} | {room_not_found, iolist()}. |
140 |
|
modify_room_config(RoomJID, Fun) -> |
141 |
5 |
case mod_muc:room_jid_to_pid(RoomJID) of |
142 |
|
{ok, _} -> |
143 |
3 |
{ok, modify_room_config_raw(RoomJID, Fun)}; |
144 |
|
{error, not_found} -> |
145 |
2 |
?ROOM_NOT_FOUND_RESULT |
146 |
|
end. |
147 |
|
|
148 |
|
-spec invite_to_room(jid:jid(), jid:jid(), jid:jid(), binary()) -> |
149 |
|
{ok | internal | room_not_found, iolist()}. |
150 |
|
invite_to_room(RoomJID, SenderJID, RecipientJID, Reason) -> |
151 |
15 |
case verify_room(RoomJID, SenderJID) of |
152 |
|
ok -> |
153 |
10 |
Attrs = case get_room_config(RoomJID) of |
154 |
|
{ok, #config{password_protected = true, password = Pass}} -> |
155 |
3 |
[{<<"password">>, Pass}]; |
156 |
|
_ -> |
157 |
7 |
[] |
158 |
|
end, |
159 |
|
%% Direct invitation: i.e. not mediated by MUC room. See XEP 0249. |
160 |
10 |
X = #xmlel{name = <<"x">>, |
161 |
|
attrs = [{<<"xmlns">>, ?NS_CONFERENCE}, |
162 |
|
{<<"jid">>, jid:to_binary(RoomJID)}, |
163 |
|
{<<"reason">>, Reason} | Attrs] |
164 |
|
}, |
165 |
10 |
Invite = message(SenderJID, RecipientJID, <<>>, [X]), |
166 |
10 |
ejabberd_router:route(SenderJID, RecipientJID, Invite), |
167 |
10 |
{ok, "Invitation sent successfully"}; |
168 |
|
Error -> |
169 |
5 |
Error |
170 |
|
end. |
171 |
|
|
172 |
|
-spec send_message_to_room(jid:jid(), jid:jid(), binary()) -> {ok, iolist()}. |
173 |
|
send_message_to_room(RoomJID, SenderJID, Message) -> |
174 |
13 |
Body = #xmlel{name = <<"body">>, |
175 |
|
children = [#xmlcdata{content = Message}]}, |
176 |
13 |
Stanza = message(SenderJID, RoomJID, <<"groupchat">>, [Body]), |
177 |
13 |
ejabberd_router:route(SenderJID, RoomJID, Stanza), |
178 |
13 |
{ok, "Message sent successfully"}. |
179 |
|
|
180 |
|
-spec send_private_message(jid:jid(), jid:jid(), binary(), binary()) -> {ok, iolist()}. |
181 |
|
send_private_message(RoomJID, SenderJID, ToNick, Message) -> |
182 |
4 |
RoomJIDRes = jid:replace_resource(RoomJID, ToNick), |
183 |
4 |
Body = #xmlel{name = <<"body">>, |
184 |
|
children = [#xmlcdata{content = Message}]}, |
185 |
4 |
X = #xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}]}, |
186 |
4 |
Stanza = message(SenderJID, RoomJID, <<"chat">>, [Body, X]), |
187 |
4 |
ejabberd_router:route(SenderJID, RoomJIDRes, Stanza), |
188 |
4 |
{ok, "Message sent successfully"}. |
189 |
|
|
190 |
|
-spec kick_user_from_room(jid:jid(), binary(), binary()) -> |
191 |
|
{ok | room_not_found | moderator_not_found, iolist()}. |
192 |
|
kick_user_from_room(RoomJID, Nick, ReasonIn) -> |
193 |
|
%% All the machinery which is already deeply embedded in the MUC |
194 |
|
%% modules will perform the neccessary checking. |
195 |
12 |
case room_moderator(RoomJID) of |
196 |
|
{ok, SenderJID} -> |
197 |
5 |
kick_user_from_room_raw(RoomJID, SenderJID, Nick, ReasonIn); |
198 |
|
{error, moderator_not_found} -> |
199 |
4 |
?MODERATOR_NOT_FOUND_RESULT; |
200 |
|
{error, not_found} -> |
201 |
3 |
?ROOM_NOT_FOUND_RESULT |
202 |
|
end. |
203 |
|
|
204 |
|
-spec kick_user_from_room(jid:jid(), jid:jid(), binary(), binary()) -> |
205 |
|
{ok | room_not_found | moderator_not_found, iolist()}. |
206 |
|
kick_user_from_room(RoomJID, UserJID, Nick, ReasonIn) -> |
207 |
|
%% All the machinery which is already deeply embedded in the MUC |
208 |
|
%% modules will perform the neccessary checking. |
209 |
3 |
case try_add_role_resource(RoomJID, UserJID, moderator) of |
210 |
|
{ok, #jid{lresource = <<>>}} -> |
211 |
1 |
?MODERATOR_RES_NOT_FOUND_RESULT; |
212 |
|
{ok, SenderJID} -> |
213 |
1 |
kick_user_from_room_raw(RoomJID, SenderJID, Nick, ReasonIn); |
214 |
|
{error, not_found} -> |
215 |
1 |
?ROOM_NOT_FOUND_RESULT |
216 |
|
end. |
217 |
|
|
218 |
|
-spec delete_room(jid:jid(), binary()) -> {ok | room_not_found, iolist()}. |
219 |
|
delete_room(RoomJID, Reason) -> |
220 |
8 |
case mod_muc_room:delete_room(RoomJID, Reason) of |
221 |
|
ok -> |
222 |
3 |
?ROOM_DELETED_SUCC_RESULT; |
223 |
|
{error, not_found} -> |
224 |
5 |
?DELETE_NONEXISTENT_ROOM_RESULT |
225 |
|
end. |
226 |
|
|
227 |
|
-spec delete_room(jid:jid(), jid:jid(), binary()) -> |
228 |
|
{ok | not_allowed | room_not_found, iolist()}. |
229 |
|
delete_room(RoomJID, OwnerJID, Reason) -> |
230 |
3 |
case mod_muc_room:is_room_owner(RoomJID, OwnerJID) of |
231 |
|
{ok, true} -> |
232 |
1 |
ok = mod_muc_room:delete_room(RoomJID, Reason), |
233 |
1 |
?ROOM_DELETED_SUCC_RESULT; |
234 |
|
{ok, false} -> |
235 |
1 |
{not_allowed, "Given user does not have permission to delete this room"}; |
236 |
|
{error, not_found} -> |
237 |
1 |
?DELETE_NONEXISTENT_ROOM_RESULT |
238 |
|
end. |
239 |
|
|
240 |
|
-spec get_room_users(jid:jid()) -> {ok, [user_map()]} | {room_not_found, iolist()}. |
241 |
|
get_room_users(RoomJID) -> |
242 |
43 |
case mod_muc_room:get_room_users(RoomJID) of |
243 |
|
{ok, Users} -> |
244 |
41 |
{ok, [to_user_map(U, true) || U <- Users]}; |
245 |
|
{error, not_found} -> |
246 |
2 |
?ROOM_NOT_FOUND_RESULT |
247 |
|
end. |
248 |
|
|
249 |
|
-spec get_room_users(jid:jid(), jid:jid()) -> |
250 |
|
{ok, [user_map()]} | {not_allowed | room_not_found, iolist()}. |
251 |
|
get_room_users(RoomJID, UserJID) -> |
252 |
4 |
case mod_muc_room:can_access_room(RoomJID, UserJID) of |
253 |
|
{ok, true} -> |
254 |
2 |
{ok, Users} = mod_muc_room:get_room_users(RoomJID), |
255 |
2 |
{ok, WithJID} = mod_muc_room:can_access_identity(RoomJID, UserJID), |
256 |
2 |
{ok, [to_user_map(U, WithJID) || U <- Users]}; |
257 |
|
{ok, false} -> |
258 |
1 |
?USER_CANNOT_ACCESS_ROOM_RESULT; |
259 |
|
{error, not_found} -> |
260 |
1 |
?ROOM_NOT_FOUND_RESULT |
261 |
|
end. |
262 |
|
|
263 |
|
-spec get_room_messages(jid:jid(), integer() | undefined, |
264 |
|
mod_mam:unix_timestamp() | undefined) -> |
265 |
|
{ok, [mod_mam:message_row()]} | {muc_server_not_found | internal, iolist()}. |
266 |
|
get_room_messages(RoomJID, PageSize, Before) -> |
267 |
5 |
case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of |
268 |
|
{ok, HostType} -> |
269 |
3 |
mod_muc_light_api:get_room_messages(HostType, RoomJID, undefined, PageSize, Before); |
270 |
|
{error, not_found} -> |
271 |
2 |
?MUC_SERVER_NOT_FOUND_RESULT |
272 |
|
end. |
273 |
|
|
274 |
|
-spec get_room_messages(jid:jid(), jid:jid(), integer() | undefined, |
275 |
|
mod_mam:unix_timestamp() | undefined) -> |
276 |
|
{ok, list()} | {muc_server_not_found | room_not_found | internal | not_allowed, iolist()}. |
277 |
|
get_room_messages(RoomJID, UserJID, PageSize, Before) -> |
278 |
5 |
case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of |
279 |
|
{ok, HostType} -> |
280 |
4 |
case mod_muc_room:can_access_room(RoomJID, UserJID) of |
281 |
|
{ok, true} -> |
282 |
2 |
mod_muc_light_api:get_room_messages(HostType, RoomJID, UserJID, |
283 |
|
PageSize, Before); |
284 |
|
{ok, false} -> |
285 |
1 |
?USER_CANNOT_ACCESS_ROOM_RESULT; |
286 |
|
{error, not_found} -> |
287 |
1 |
?ROOM_NOT_FOUND_RESULT |
288 |
|
end; |
289 |
|
{error, not_found} -> |
290 |
1 |
?MUC_SERVER_NOT_FOUND_RESULT |
291 |
|
end. |
292 |
|
|
293 |
|
-spec get_room_affiliations(jid:jid(), jid:jid(), mod_muc:affiliation() | undefined) -> |
294 |
|
{ok, [aff_item()]} | {not_allowed | room_not_found, iolist()}. |
295 |
|
get_room_affiliations(RoomJID, UserJID, AffType) -> |
296 |
5 |
case mod_muc_room:can_access_room(RoomJID, UserJID) of |
297 |
|
{ok, true} -> |
298 |
3 |
get_room_affiliations(RoomJID, AffType); |
299 |
|
{ok, false} -> |
300 |
1 |
?USER_CANNOT_ACCESS_ROOM_RESULT; |
301 |
|
{error, not_found} -> |
302 |
1 |
?ROOM_NOT_FOUND_RESULT |
303 |
|
end. |
304 |
|
|
305 |
|
-spec get_room_affiliations(jid:jid(), mod_muc:affiliation() | undefined) -> |
306 |
|
{ok, [aff_item()]} | {room_not_found, iolist()}. |
307 |
|
get_room_affiliations(RoomJID, AffType) -> |
308 |
37 |
case room_users_aff(RoomJID) of |
309 |
|
{ok, Affiliations} -> |
310 |
35 |
Res = filter_affs_by_type(AffType, Affiliations), |
311 |
35 |
{ok, Res}; |
312 |
|
{error, not_found} -> |
313 |
2 |
?ROOM_NOT_FOUND_RESULT |
314 |
|
end. |
315 |
|
|
316 |
|
-spec set_affiliation(jid:jid(), jid:jid(), mod_muc:affiliation()) -> |
317 |
|
{ok | room_not_found | not_allowed, iolist()}. |
318 |
|
set_affiliation(RoomJID, UserJID, Affiliation) -> |
319 |
20 |
case room_users_aff(RoomJID) of |
320 |
|
{ok, Affs} -> |
321 |
18 |
{OwnerJID, owner} = hd(filter_affs_by_type(owner, Affs)), |
322 |
18 |
set_affiliation(RoomJID, OwnerJID, UserJID, Affiliation); |
323 |
|
{error, not_found} -> |
324 |
2 |
?ROOM_NOT_FOUND_RESULT |
325 |
|
end. |
326 |
|
|
327 |
|
-spec set_affiliation(jid:jid(), jid:jid(), jid:jid(), mod_muc:affiliation()) -> |
328 |
|
{ok | room_not_found | not_allowed, iolist()}. |
329 |
|
set_affiliation(RoomJID, FromJID, UserJID, Affiliation) -> |
330 |
38 |
AffItem = affiliation_item(UserJID, Affiliation), |
331 |
38 |
case mod_muc_room:set_admin_items(RoomJID, FromJID, [AffItem]) of |
332 |
|
ok -> |
333 |
30 |
{ok, "Affiliation set successfully"}; |
334 |
|
{error, not_found} -> |
335 |
1 |
?ROOM_NOT_FOUND_RESULT; |
336 |
|
{error, #xmlel{} = E} -> |
337 |
7 |
format_xml_error(E, Affiliation) |
338 |
|
end. |
339 |
|
|
340 |
|
-spec set_role(jid:jid(), binary(), mod_muc:role()) -> |
341 |
|
{ok | moderator_not_found | room_not_found | not_allowed | cannot_modify, iolist()}. |
342 |
|
set_role(RoomJID, Nick, Role) -> |
343 |
17 |
case room_moderator(RoomJID) of |
344 |
|
{ok, ModJID} -> |
345 |
12 |
case mod_muc_room:set_admin_items(RoomJID, ModJID, [role_item(Nick, Role)]) of |
346 |
|
ok -> |
347 |
9 |
?SET_ROLE_SUCC_RESULT; |
348 |
|
{error, #xmlel{} = E} -> |
349 |
3 |
format_xml_error(E, Role) |
350 |
|
end; |
351 |
|
{error, moderator_not_found} -> |
352 |
3 |
?MODERATOR_NOT_FOUND_RESULT; |
353 |
|
{error, not_found} -> |
354 |
2 |
?ROOM_NOT_FOUND_RESULT |
355 |
|
end. |
356 |
|
|
357 |
|
-spec set_role(jid:jid(), jid:jid(), binary(), mod_muc:role()) -> |
358 |
|
{ok | moderator_not_found | room_not_found | not_allowed | cannot_modify, iolist()}. |
359 |
|
set_role(RoomJID, UserJID, Nick, Role) -> |
360 |
7 |
RoleItem = role_item(Nick, Role), |
361 |
7 |
case try_add_role_resource(RoomJID, UserJID, moderator) of |
362 |
|
{ok, ModJID} -> |
363 |
6 |
case mod_muc_room:set_admin_items(RoomJID, ModJID, [RoleItem]) of |
364 |
|
ok -> |
365 |
4 |
?SET_ROLE_SUCC_RESULT; |
366 |
|
{error, #xmlel{} = E} -> |
367 |
2 |
format_xml_error(E, Role) |
368 |
|
end; |
369 |
|
{error, not_found} -> |
370 |
1 |
?ROOM_NOT_FOUND_RESULT |
371 |
|
end. |
372 |
|
|
373 |
|
-spec enter_room(jid:jid(), jid:jid(), binary() | undefined) -> {ok, iolist()}. |
374 |
|
enter_room(RoomJID, UserJID, Password) -> |
375 |
16 |
Presence = presence(UserJID, RoomJID, Password), |
376 |
16 |
ejabberd_router:route(UserJID, RoomJID, Presence), |
377 |
16 |
{ok, "Entering room message sent successfully"}. |
378 |
|
|
379 |
|
-spec exit_room(jid:jid(), jid:jid()) -> {ok, iolist()}. |
380 |
|
exit_room(RoomJID, UserJID) -> |
381 |
4 |
Presence = exit_room_presence(UserJID, RoomJID), |
382 |
4 |
ejabberd_router:route(UserJID, RoomJID, Presence), |
383 |
4 |
{ok, "Exiting room message sent successfully"}. |
384 |
|
|
385 |
|
%% Internal |
386 |
|
|
387 |
|
-spec kick_user_from_room_raw(jid:jid(), jid:jid(), binary(), binary()) -> {ok, iolist()}. |
388 |
|
kick_user_from_room_raw(RoomJID, ModJID, Nick, ReasonIn) -> |
389 |
6 |
Reason = #xmlel{name = <<"reason">>, |
390 |
|
children = [#xmlcdata{content = ReasonIn}] |
391 |
|
}, |
392 |
6 |
Item = #xmlel{name = <<"item">>, |
393 |
|
attrs = [{<<"nick">>, Nick}, |
394 |
|
{<<"role">>, <<"none">>}], |
395 |
|
children = [ Reason ] |
396 |
|
}, |
397 |
6 |
IQ = iq(<<"set">>, ModJID, RoomJID, [ query(?NS_MUC_ADMIN, [ Item ]) ]), |
398 |
6 |
ejabberd_router:route(ModJID, RoomJID, IQ), |
399 |
6 |
{ok, "Kick message sent successfully"}. |
400 |
|
|
401 |
|
-spec try_add_role_resource(jid:jid(), jid:jid(), mod_muc:role()) -> |
402 |
|
{ok, jid:jid()} | {error, not_found}. |
403 |
|
try_add_role_resource(RoomJID, InUserJID, Role) -> |
404 |
10 |
case mod_muc_room:get_room_users(RoomJID) of |
405 |
|
{ok, Users} -> |
406 |
8 |
case [UserJID || #user{jid = UserJID, role = Role2} <- Users, |
407 |
15 |
jid:are_bare_equal(InUserJID, UserJID), Role =:= Role2] of |
408 |
|
[UserJID | _] -> |
409 |
4 |
{ok, UserJID}; |
410 |
|
_ -> |
411 |
4 |
{ok, InUserJID} |
412 |
|
end; |
413 |
|
Error -> |
414 |
2 |
Error |
415 |
|
end. |
416 |
|
|
417 |
9 |
filter_affs_by_type(undefined, Affs) -> Affs; |
418 |
44 |
filter_affs_by_type(Type, Affs) -> [Aff || Aff = {_, T} <- Affs, T =:= Type]. |
419 |
|
|
420 |
|
format_xml_error(#xmlel{name = <<"error">>} = E, Value) -> |
421 |
12 |
case unwrap_xml_error(E) of |
422 |
|
{<<"406">>, <<"modify">>, <<"not-acceptable">>} -> |
423 |
3 |
Msg = xml:get_path_s(E, [{elem, <<"text">>}, cdata]), |
424 |
3 |
{cannot_modify, Msg}; |
425 |
|
{<<"403">>, <<"auth">>, <<"forbidden">>} -> |
426 |
6 |
{not_allowed, no_permission_msg("affiliation", Value)}; |
427 |
|
{<<"405">>, <<"cancel">>, <<"not-allowed">>} -> |
428 |
3 |
{not_allowed, no_permission_msg("role", Value)} |
429 |
|
end. |
430 |
|
|
431 |
|
no_permission_msg(Op, Value) -> |
432 |
9 |
io_lib:format("Given user does not have permission to set the ~p ~s", [Value, Op]). |
433 |
|
|
434 |
|
unwrap_xml_error(#xmlel{attrs = [{<<"code">>, Code}, {<<"type">>, Type}], |
435 |
|
children = [#xmlel{name = Condition} | _]}) -> |
436 |
12 |
{Code, Type, Condition}. |
437 |
|
|
438 |
|
-spec modify_room_config_raw(jid:jid(), room_conf_mod_fun()) -> mod_muc_room:config(). |
439 |
|
modify_room_config_raw(RoomJID, Fun) -> |
440 |
4 |
{ok, Config} = mod_muc_room:get_room_config(RoomJID), |
441 |
4 |
{ok, NewConfig2} = mod_muc_room:change_room_config(RoomJID, Fun(Config)), |
442 |
4 |
NewConfig2. |
443 |
|
|
444 |
|
-spec to_user_map(mod_muc_room:user(), boolean()) -> user_map(). |
445 |
|
to_user_map(#user{role = Role, nick = Nick}, false = _WithJID) -> |
446 |
2 |
#{jid => null, role => Role, nick => Nick}; |
447 |
|
to_user_map(#user{jid = JID, role = Role, nick = Nick}, true) -> |
448 |
59 |
#{jid => JID, role => Role, nick => Nick}. |
449 |
|
|
450 |
|
-spec room_to_item(tuple(), jid:lserver(), jid:jid()) -> {true, short_room_desc()} | false. |
451 |
|
room_to_item({{Name, _}, Pid}, MUCServer, From) -> |
452 |
45 |
case catch gen_fsm_compat:sync_send_all_state_event( |
453 |
|
Pid, {get_disco_item, From, <<"en">>}, 100) of |
454 |
|
{item, Desc} -> |
455 |
45 |
Map = room_desc_to_map(Desc), |
456 |
45 |
{true, Map#{jid => jid:to_binary({Name, MUCServer, <<>>})}}; |
457 |
|
_ -> |
458 |
:-( |
false |
459 |
|
end. |
460 |
|
|
461 |
|
-spec room_desc_to_map(binary()) -> #{title := binary(), private => boolean(), |
462 |
|
users_number => non_neg_integer()}. |
463 |
|
room_desc_to_map(Desc) -> |
464 |
45 |
MP = "(\\S+)( \\((private, )?(\\d+)\\))?", |
465 |
45 |
{match, [TitlePos, _, PrivatePos, NumberPos]} = re:run(Desc, MP, [{capture, [1, 2, 3, 4]}]), |
466 |
45 |
Title = binary:part(Desc, TitlePos), |
467 |
45 |
case NumberPos of |
468 |
|
{-1, 0} -> |
469 |
17 |
#{title => Title}; |
470 |
|
_ -> |
471 |
28 |
Private = {-1, 0} =/= PrivatePos, |
472 |
28 |
Number = binary_to_integer(binary:part(Desc, NumberPos)), |
473 |
28 |
#{title => Title, private => Private, users_number => Number} |
474 |
|
end. |
475 |
|
|
476 |
|
-spec verify_room(jid:jid(), jid:jid()) -> ok | {internal | room_not_found, term()}. |
477 |
|
verify_room(BareRoomJID, OwnerJID) -> |
478 |
33 |
case mod_muc_room:can_access_room(BareRoomJID, OwnerJID) of |
479 |
|
{ok, true} -> |
480 |
24 |
ok; |
481 |
|
{ok, false} -> |
482 |
:-( |
{internal, "Room is locked"}; |
483 |
|
{error, not_found} -> |
484 |
9 |
?ROOM_NOT_FOUND_RESULT |
485 |
|
end. |
486 |
|
|
487 |
|
role_item(Nick, Role) -> |
488 |
19 |
#xmlel{name = <<"item">>, attrs = [{<<"nick">>, Nick}, {<<"role">>, atom_to_binary(Role)}]}. |
489 |
|
|
490 |
|
affiliation_item(JID, Aff) -> |
491 |
38 |
#xmlel{name = <<"item">>, attrs = [{<<"jid">>, jid:to_binary(JID)}, |
492 |
|
{<<"affiliation">>, atom_to_binary(Aff)}]}. |
493 |
|
|
494 |
|
iq(Type, Sender, Recipient, Children) when is_binary(Type), is_list(Children) -> |
495 |
24 |
Addresses = address_attributes(Sender, Recipient), |
496 |
24 |
#xmlel{name = <<"iq">>, |
497 |
|
attrs = Addresses ++ [{<<"type">>, Type}], |
498 |
|
children = Children |
499 |
|
}. |
500 |
|
|
501 |
|
message(Sender, Recipient, Type, Contents) when is_binary(Type), is_list(Contents) -> |
502 |
27 |
Addresses = address_attributes(Sender, Recipient), |
503 |
27 |
Attributes = case Type of |
504 |
10 |
<<>> -> Addresses; |
505 |
17 |
_ -> [{<<"type">>, Type} | Addresses] |
506 |
|
end, |
507 |
27 |
#xmlel{name = <<"message">>, |
508 |
|
attrs = Attributes, |
509 |
|
children = Contents}. |
510 |
|
|
511 |
|
query(XMLNameSpace, Children) |
512 |
|
when is_binary(XMLNameSpace), is_list(Children) -> |
513 |
24 |
#xmlel{name = <<"query">>, |
514 |
|
attrs = [{<<"xmlns">>, XMLNameSpace}], |
515 |
|
children = Children}. |
516 |
|
|
517 |
|
presence(Sender, Recipient, Password) -> |
518 |
34 |
Children = case Password of |
519 |
26 |
undefined -> []; |
520 |
8 |
_ -> [#xmlel{name = <<"password">>, |
521 |
|
children = [#xmlcdata{content = Password}]}] |
522 |
|
end, |
523 |
34 |
#xmlel{name = <<"presence">>, |
524 |
|
attrs = address_attributes(Sender, Recipient), |
525 |
|
children = [#xmlel{name = <<"x">>, |
526 |
|
attrs = [{<<"xmlns">>, ?NS_MUC}], |
527 |
|
children = Children}]}. |
528 |
|
|
529 |
|
exit_room_presence(Sender, Recipient) -> |
530 |
4 |
#xmlel{name = <<"presence">>, |
531 |
|
attrs = [{<<"type">>, <<"unavailable">>} | address_attributes(Sender, Recipient)], |
532 |
|
children = [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}]}]}. |
533 |
|
|
534 |
|
declination(Sender, Recipient) -> |
535 |
18 |
iq(<<"set">>, Sender, Recipient, [data_submission()]). |
536 |
|
|
537 |
|
data_submission() -> |
538 |
18 |
query(?NS_MUC_OWNER, [mongoose_data_forms:form(#{type => <<"submit">>})]). |
539 |
|
|
540 |
|
address_attributes(Sender, Recipient) -> |
541 |
89 |
[{<<"from">>, jid:to_binary(Sender)}, |
542 |
|
{<<"to">>, jid:to_binary(Recipient)}]. |
543 |
|
|
544 |
|
room_moderator(RoomJID) -> |
545 |
29 |
case mod_muc_room:get_room_users(RoomJID) of |
546 |
|
{ok, Users} -> |
547 |
24 |
case [UserJID || #user{jid = UserJID, role = moderator} <- Users] of |
548 |
17 |
[ModJID | _] -> {ok, ModJID}; |
549 |
7 |
[] -> {error, moderator_not_found} |
550 |
|
end; |
551 |
|
Error -> |
552 |
5 |
Error |
553 |
|
end. |
554 |
|
|
555 |
|
room_users_aff(RoomJID) -> |
556 |
57 |
case mod_muc_room:get_room_affiliations(RoomJID) of |
557 |
|
{ok, Affs} -> |
558 |
53 |
{ok, format_affs(Affs)}; |
559 |
|
{error, Error} -> |
560 |
4 |
{error, Error} |
561 |
|
end. |
562 |
|
|
563 |
|
format_affs(AffsMap) -> |
564 |
53 |
lists:map(fun |
565 |
38 |
({K, {V, <<>>}}) -> {K, V}; |
566 |
53 |
({K, V}) -> {K, V} |
567 |
|
end, maps:to_list(AffsMap)). |