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