1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2016 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%% |
16 |
|
%% Author: Joseph Yiasemides <joseph.yiasemides@erlang-solutions.com> |
17 |
|
%% Description: Administration commands for Mult-user Chat (MUC) |
18 |
|
%%============================================================================== |
19 |
|
|
20 |
|
-module(mod_muc_commands). |
21 |
|
|
22 |
|
-behaviour(gen_mod). |
23 |
|
-behaviour(mongoose_module_metrics). |
24 |
|
|
25 |
|
-export([start/2, stop/1, supported_features/0]). |
26 |
|
|
27 |
|
-export([create_instant_room/4]). |
28 |
|
-export([invite_to_room/5]). |
29 |
|
-export([send_message_to_room/4]). |
30 |
|
-export([kick_user_from_room/3]). |
31 |
|
|
32 |
|
-ignore_xref([create_instant_room/4, invite_to_room/5, kick_user_from_room/3, |
33 |
|
send_message_to_room/4]). |
34 |
|
|
35 |
|
-include("mongoose.hrl"). |
36 |
|
-include("jlib.hrl"). |
37 |
|
-include("mod_muc_room.hrl"). |
38 |
|
|
39 |
|
start(_, _) -> |
40 |
316 |
mongoose_commands:register(commands()). |
41 |
|
|
42 |
|
stop(_) -> |
43 |
316 |
mongoose_commands:unregister(commands()). |
44 |
|
|
45 |
|
-spec supported_features() -> [atom()]. |
46 |
|
supported_features() -> |
47 |
146 |
[dynamic_domains]. |
48 |
|
|
49 |
|
commands() -> |
50 |
632 |
[ |
51 |
|
[{name, create_muc_room}, |
52 |
|
{category, <<"mucs">>}, |
53 |
|
{desc, <<"Create a MUC room.">>}, |
54 |
|
{module, ?MODULE}, |
55 |
|
{function, create_instant_room}, |
56 |
|
{action, create}, |
57 |
|
{identifiers, [domain]}, |
58 |
|
{args, |
59 |
|
%% The argument `domain' is what we normally term the XMPP |
60 |
|
%% domain, `name' is the room name, `owner' is the XMPP entity |
61 |
|
%% that would normally request an instant MUC room. |
62 |
|
[{domain, binary}, |
63 |
|
{name, binary}, |
64 |
|
{owner, binary}, |
65 |
|
{nick, binary}]}, |
66 |
|
{result, {name, binary}}], |
67 |
|
|
68 |
|
[{name, invite_to_muc_room}, |
69 |
|
{category, <<"mucs">>}, |
70 |
|
{subcategory, <<"participants">>}, |
71 |
|
{desc, <<"Send a MUC room invite from one user to another.">>}, |
72 |
|
{module, ?MODULE}, |
73 |
|
{function, invite_to_room}, |
74 |
|
{action, create}, |
75 |
|
{identifiers, [domain, name]}, |
76 |
|
{args, |
77 |
|
[{domain, binary}, |
78 |
|
{name, binary}, |
79 |
|
{sender, binary}, |
80 |
|
{recipient, binary}, |
81 |
|
{reason, binary} |
82 |
|
]}, |
83 |
|
{result, ok}], |
84 |
|
|
85 |
|
[{name, send_message_to_room}, |
86 |
|
{category, <<"mucs">>}, |
87 |
|
{subcategory, <<"messages">>}, |
88 |
|
{desc, <<"Send a message to a MUC room from a given user.">>}, |
89 |
|
{module, ?MODULE}, |
90 |
|
{function, send_message_to_room}, |
91 |
|
{action, create}, |
92 |
|
{identifiers, [domain, name]}, |
93 |
|
{args, |
94 |
|
[{domain, binary}, |
95 |
|
{name, binary}, |
96 |
|
{from, binary}, |
97 |
|
{body, binary} |
98 |
|
]}, |
99 |
|
{result, ok}], |
100 |
|
|
101 |
|
[{name, kick_user_from_room}, |
102 |
|
{category, <<"mucs">>}, |
103 |
|
{desc, |
104 |
|
<<"Kick a user from a MUC room (on behalf of a moderator).">>}, |
105 |
|
{module, ?MODULE}, |
106 |
|
{function, kick_user_from_room}, |
107 |
|
{action, delete}, |
108 |
|
{identifiers, [domain, name, nick]}, |
109 |
|
{args, |
110 |
|
[{domain, binary}, |
111 |
|
{name, binary}, |
112 |
|
{nick, binary} |
113 |
|
]}, |
114 |
|
{result, ok}] |
115 |
|
|
116 |
|
]. |
117 |
|
|
118 |
|
create_instant_room(Domain, Name, Owner, Nick) -> |
119 |
|
%% Because these stanzas are sent on the owner's behalf through |
120 |
|
%% the HTTP API, they will certainly recieve stanzas as a |
121 |
|
%% consequence, even if their client(s) did not initiate this. |
122 |
5 |
OwnerJID = jid:binary_to_bare(Owner), |
123 |
5 |
BareRoomJID = #jid{server = MUCDomain} = room_jid(Domain, Name), |
124 |
5 |
UserRoomJID = jid:make(Name, MUCDomain, Nick), |
125 |
|
%% Send presence to create a room. |
126 |
5 |
ejabberd_router:route(OwnerJID, UserRoomJID, |
127 |
|
presence(OwnerJID, UserRoomJID)), |
128 |
|
%% Send IQ set to unlock the room. |
129 |
5 |
ejabberd_router:route(OwnerJID, BareRoomJID, |
130 |
|
declination(OwnerJID, BareRoomJID)), |
131 |
5 |
case verify_room(BareRoomJID, OwnerJID) of |
132 |
|
ok -> |
133 |
5 |
Name; |
134 |
|
Error -> |
135 |
:-( |
Error |
136 |
|
end. |
137 |
|
|
138 |
|
invite_to_room(Domain, Name, Sender, Recipient, Reason) -> |
139 |
10 |
case mongoose_stanza_helper:parse_from_to(Sender, Recipient) of |
140 |
|
{ok, SenderJid, RecipientJid} -> |
141 |
6 |
RoomJid = room_jid(Domain, Name), |
142 |
6 |
case verify_room(RoomJid, SenderJid) of |
143 |
|
ok -> |
144 |
|
%% Direct invitation: i.e. not mediated by MUC room. See XEP 0249. |
145 |
3 |
X = #xmlel{name = <<"x">>, |
146 |
|
attrs = [{<<"xmlns">>, ?NS_CONFERENCE}, |
147 |
|
{<<"jid">>, jid:to_binary(RoomJid)}, |
148 |
|
{<<"reason">>, Reason}] |
149 |
|
}, |
150 |
3 |
Invite = message(SenderJid, RecipientJid, <<>>, [ X ]), |
151 |
3 |
ejabberd_router:route(SenderJid, RecipientJid, Invite); |
152 |
|
Error -> |
153 |
3 |
Error |
154 |
|
end; |
155 |
|
Error -> |
156 |
4 |
Error |
157 |
|
end. |
158 |
|
|
159 |
|
send_message_to_room(Domain, Name, Sender, Message) -> |
160 |
2 |
RoomJid = room_jid(Domain, Name), |
161 |
2 |
case jid:from_binary(Sender) of |
162 |
|
error -> |
163 |
:-( |
error; |
164 |
|
SenderJid -> |
165 |
2 |
Body = #xmlel{name = <<"body">>, |
166 |
|
children = [ #xmlcdata{ content = Message } ] |
167 |
|
}, |
168 |
2 |
Stanza = message(SenderJid, RoomJid, <<"groupchat">>, [ Body ]), |
169 |
2 |
ejabberd_router:route(SenderJid, RoomJid, Stanza) |
170 |
|
end. |
171 |
|
|
172 |
|
kick_user_from_room(Domain, Name, Nick) -> |
173 |
|
%% All the machinery which is already deeply embedded in the MUC |
174 |
|
%% modules will perform the neccessary checking. |
175 |
1 |
RoomJid = room_jid(Domain, Name), |
176 |
1 |
SenderJid = room_moderator(RoomJid), |
177 |
1 |
Reason = #xmlel{name = <<"reason">>, |
178 |
|
children = [ #xmlcdata{ content = reason() } ] |
179 |
|
}, |
180 |
1 |
Item = #xmlel{name = <<"item">>, |
181 |
|
attrs = [{<<"nick">>, Nick}, |
182 |
|
{<<"role">>, <<"none">>}], |
183 |
|
children = [ Reason ] |
184 |
|
}, |
185 |
1 |
IQ = iq(<<"set">>, SenderJid, RoomJid, [ query(?NS_MUC_ADMIN, [ Item ]) ]), |
186 |
1 |
ejabberd_router:route(SenderJid, RoomJid, IQ). |
187 |
|
|
188 |
|
%%-------------------------------------------------------------------- |
189 |
|
%% Ancillary |
190 |
|
%%-------------------------------------------------------------------- |
191 |
|
|
192 |
|
-spec room_jid(jid:lserver(), binary()) -> jid:jid() | error. |
193 |
|
room_jid(Domain, Name) -> |
194 |
14 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(Domain), |
195 |
14 |
MUCDomain = mod_muc:server_host_to_muc_host(HostType, Domain), |
196 |
14 |
jid:make(Name, MUCDomain, <<>>). |
197 |
|
|
198 |
|
-spec verify_room(jid:jid(), jid:jid()) -> |
199 |
|
ok | {error, internal | not_found, term()}. |
200 |
|
verify_room(BareRoomJID, OwnerJID) -> |
201 |
11 |
case mod_muc_room:can_access_room(BareRoomJID, OwnerJID) of |
202 |
|
{ok, true} -> |
203 |
8 |
ok; |
204 |
|
{ok, false} -> |
205 |
:-( |
{error, internal, "room is locked"}; |
206 |
|
{error, not_found} -> |
207 |
3 |
{error, not_found, "room does not exist"} |
208 |
|
end. |
209 |
|
|
210 |
|
iq(Type, Sender, Recipient, Children) |
211 |
|
when is_binary(Type), is_list(Children) -> |
212 |
6 |
Addresses = address_attributes(Sender, Recipient), |
213 |
6 |
#xmlel{name = <<"iq">>, |
214 |
|
attrs = Addresses ++ [{<<"type">>, Type}], |
215 |
|
children = Children |
216 |
|
}. |
217 |
|
|
218 |
|
message(Sender, Recipient, Type, Contents) |
219 |
|
when is_binary(Type), is_list(Contents) -> |
220 |
5 |
Addresses = address_attributes(Sender, Recipient), |
221 |
5 |
Attributes = case Type of |
222 |
3 |
<<>> -> Addresses; |
223 |
2 |
_ -> [{<<"type">>, Type}|Addresses] |
224 |
|
end, |
225 |
5 |
#xmlel{name = <<"message">>, |
226 |
|
attrs = Attributes, |
227 |
|
children = Contents |
228 |
|
}. |
229 |
|
|
230 |
|
query(XMLNameSpace, Children) |
231 |
|
when is_binary(XMLNameSpace), is_list(Children) -> |
232 |
6 |
#xmlel{name = <<"query">>, |
233 |
|
attrs = [{<<"xmlns">>, XMLNameSpace}], |
234 |
|
children = Children |
235 |
|
}. |
236 |
|
|
237 |
|
presence(Sender, Recipient) -> |
238 |
5 |
#xmlel{name = <<"presence">>, |
239 |
|
attrs = address_attributes(Sender, Recipient), |
240 |
|
children = [#xmlel{name = <<"x">>, |
241 |
|
attrs = [{<<"xmlns">>, ?NS_MUC}]}] |
242 |
|
}. |
243 |
|
|
244 |
|
declination(Sender, Recipient) -> |
245 |
5 |
iq(<<"set">>, Sender, Recipient, [ data_submission() ]). |
246 |
|
|
247 |
|
data_submission() -> |
248 |
5 |
query(?NS_MUC_OWNER, [#xmlel{name = <<"x">>, |
249 |
|
attrs = [{<<"xmlns">>, ?NS_XDATA}, |
250 |
|
{<<"type">>, <<"submit">>}]}]). |
251 |
|
|
252 |
|
address_attributes(Sender, Recipient) -> |
253 |
16 |
[ |
254 |
|
{<<"from">>, jid:to_binary(Sender)}, |
255 |
|
{<<"to">>, jid:to_binary(Recipient)} |
256 |
|
]. |
257 |
|
|
258 |
|
reason() -> |
259 |
1 |
<<"Kicked through HTTP Administration API.">>. |
260 |
|
|
261 |
|
|
262 |
|
room_moderator(RoomJID) -> |
263 |
1 |
[JIDStruct|_] = |
264 |
1 |
[ UserJID |
265 |
|
|| #user{ jid = UserJID, |
266 |
1 |
role = moderator } <- room_users(RoomJID) ], |
267 |
1 |
JIDStruct. |
268 |
|
|
269 |
|
room_users(RoomJID) -> |
270 |
1 |
{ok, Affiliations} = mod_muc_room:get_room_users(RoomJID), |
271 |
1 |
Affiliations. |