• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1296

19 Jan 2026 11:25AM UTC coverage: 33.562% (+0.09%) from 33.468%
1296

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

0 of 4 new or added lines in 1 file covered. (0.0%)

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.99
/src/mod_muc_admin.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_muc_admin.erl
3
%%% Author  : Badlop <badlop@process-one.net>
4
%%% Purpose : Tools for additional MUC administration
5
%%% Created : 8 Sep 2007 by Badlop <badlop@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2026   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
-module(mod_muc_admin).
27
-author('badlop@process-one.net').
28

29
-behaviour(gen_mod).
30

31
-export([start/2, stop/1, reload/3, depends/2, mod_doc/0,
32
         muc_online_rooms/1, muc_online_rooms_by_regex/2,
33
         muc_register_nick/3, muc_register_nick/4,
34
         muc_unregister_nick/2, muc_unregister_nick/3,
35
         muc_get_registered_nick/3,
36
         muc_get_registered_nicks/1,
37
         create_room_with_opts/4, create_room/3, destroy_room/2,
38
         create_rooms_file/1, destroy_rooms_file/1,
39
         rooms_unused_list/2, rooms_unused_destroy/2,
40
         rooms_empty_list/1, rooms_empty_destroy/1, rooms_empty_destroy_restuple/1,
41
         get_user_rooms/2, get_user_subscriptions/2, get_room_occupants/2,
42
         get_room_occupants_number/2, send_direct_invitation/5,
43
         change_room_option/4, get_room_options/2,
44
         set_room_affiliation/4, set_room_affiliation/5, get_room_affiliations/2,
45
         get_room_affiliations_v3/2, get_room_affiliation/3,
46
         subscribe_room/4, subscribe_room/6,
47
         subscribe_room_many/3, subscribe_room_many_v3/4,
48
         unsubscribe_room/2, unsubscribe_room/4, get_subscribers/2,
49
         get_room_serverhost/1,
50
         web_menu_main/2, web_page_main/2,
51
         web_menu_host/3, web_page_host/3,
52
         web_menu_hostuser/4, web_page_hostuser/4,
53
         webadmin_muc/2,
54
         mod_opt_type/1, mod_options/1,
55
         get_commands_spec/0, find_hosts/1, room_diagnostics/2,
56
         get_room_pid/2, get_room_history/2, muc_online_rooms_count/1]).
57

58
-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/4]).
59

60
-include("logger.hrl").
61
-include_lib("xmpp/include/xmpp.hrl").
62
-include("mod_muc.hrl").
63
-include("mod_muc_room.hrl").
64
-include("ejabberd_http.hrl").
65
-include("ejabberd_web_admin.hrl").
66
-include("ejabberd_commands.hrl").
67
-include("translate.hrl").
68

69
%%----------------------------
70
%% gen_mod
71
%%----------------------------
72

73
start(_Host, _Opts) ->
74
    {ok, [{commands, get_commands_spec()},
90✔
75
          {hook, webadmin_menu_main, web_menu_main, 50, global},
76
          {hook, webadmin_page_main, web_page_main, 50, global},
77
          {hook, webadmin_menu_host, web_menu_host, 50},
78
          {hook, webadmin_page_host, web_page_host, 50},
79
          {hook, webadmin_menu_hostuser, web_menu_hostuser, 50},
80
          {hook, webadmin_page_hostuser, web_page_hostuser, 50}
81
         ]}.
82

83
stop(_Host) ->
84
    ok.
90✔
85

86
reload(_Host, _NewOpts, _OldOpts) ->
87
    ok.
×
88

89
depends(_Host, _Opts) ->
90
    [{mod_muc, hard}].
108✔
91

92
%%%
93
%%% Register commands
94
%%%
95

96
get_commands_spec() ->
97
    [
98
     #ejabberd_commands{name = muc_online_rooms, tags = [muc],
90✔
99
                       desc = "List existing rooms",
100
                       longdesc = "Ask for a specific host, or `global` to use all vhosts.",
101
                       policy = admin,
102
                       module = ?MODULE, function = muc_online_rooms,
103
                       args_desc = ["MUC service, or `global` for all"],
104
                       args_example = ["conference.example.com"],
105
                       result_desc = "List of rooms JIDs",
106
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
107
                       args = [{service, binary}],
108
                       args_rename = [{host, service}],
109
                       result = {rooms, {list, {room, string}}}},
110
     #ejabberd_commands{name = muc_online_rooms_count, tags = [muc],
111
                       desc = "Return number of online rooms",
112
                       longdesc = "Ask for a specific host, or `global` to use all vhosts.",
113
                       policy = admin,
114
                       module = ?MODULE, function = muc_online_rooms_count,
115
                       args_desc = ["MUC service, or `global` for all"],
116
                       args_example = ["conference.example.com"],
117
                       result_desc = "Number of active rooms",
118
                       result_example = 2137,
119
                       args = [{service, binary}],
120
                       args_rename = [{host, service}],
121
                       result = {count, integer}},
122
        #ejabberd_commands{name = muc_online_rooms_by_regex, tags = [muc],
123
                       desc = "List existing rooms filtered by regexp",
124
                       longdesc = "Ask for a specific host, or `global` to use all vhosts.",
125
                       policy = admin,
126
                       module = ?MODULE, function = muc_online_rooms_by_regex,
127
                       args_desc = ["MUC service, or `global` for all",
128
                                    "Regex pattern for room name"],
129
                       args_example = ["conference.example.com", "^prefix"],
130
                       result_desc = "List of rooms with summary",
131
                       result_example = [{"room1@conference.example.com", "true", 10},
132
                                         {"room2@conference.example.com", "false", 10}],
133
                       args = [{service, binary}, {regex, binary}],
134
                       args_rename = [{host, service}],
135
                       result = {rooms, {list, {room, {tuple,
136
                                                          [{jid, string},
137
                                                           {public, string},
138
                                                           {participants, integer}
139
                                                          ]}}}}},
140
     #ejabberd_commands{name = muc_register_nick, tags = [muc],
141
                       desc = "Register a nick to a User JID in a MUC service",
142
                       module = ?MODULE, function = muc_register_nick,
143
                       args_desc = ["Nick", "User JID", "Service"],
144
                       args_example = [<<"Tim">>, <<"tim@example.org">>, <<"conference.example.org">>],
145
                       args = [{nick, binary}, {jid, binary}, {service, binary}],
146
                       args_rename = [{host, service}],
147
                       result = {res, rescode}},
148
     #ejabberd_commands{name = muc_register_nick, tags = [muc],
149
                       desc = "Register a nick to a User JID in a MUC service",
150
                       module = ?MODULE, function = muc_register_nick,
151
                       version = 3,
152
                       note = "updated in 24.12",
153
                       args_desc = ["nick", "user name", "user host", "MUC service"],
154
                       args_example = [<<"Tim">>, <<"tim">>, <<"example.org">>, <<"conference.example.org">>],
155
                       args = [{nick, binary}, {user, binary}, {host, binary}, {service, binary}],
156
                       args_rename = [{host, service}],
157
                       result = {res, rescode}},
158
     #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
159
                       desc = "Unregister the nick registered by that account in the MUC service",
160
                       module = ?MODULE, function = muc_unregister_nick,
161
                       args_desc = ["User JID", "MUC service"],
162
                       args_example = [<<"tim@example.org">>, <<"conference.example.org">>],
163
                       args = [{jid, binary}, {service, binary}],
164
                       args_rename = [{host, service}],
165
                       result = {res, rescode}},
166
     #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
167
                       desc = "Unregister the nick registered by that account in the MUC service",
168
                       module = ?MODULE, function = muc_unregister_nick,
169
                       version = 3,
170
                       note = "updated in 24.12",
171
                       args_desc = ["user name", "user host", "MUC service"],
172
                       args_example = [<<"tim">>, <<"example.org">>, <<"conference.example.org">>],
173
                       args = [{user, binary}, {host, binary}, {service, binary}],
174
                       args_rename = [{host, service}],
175
                       result = {res, rescode}},
176
     #ejabberd_commands{name = muc_get_registered_nick, tags = [muc],
177
                       desc = "Get nick registered for that account in the MUC service",
178
                       module = ?MODULE, function = muc_get_registered_nick,
179
                       note = "added in 25.10",
180
                       args_desc = ["user name", "user host", "MUC service"],
181
                       args_example = [<<"tim">>, <<"example.org">>, <<"conference.example.org">>],
182
                       args = [{user, binary}, {host, binary}, {service, binary}],
183
                       result_desc = "nick registered",
184
                       result_example = ["Tim"],
185
                       result = {nick, string}},
186
     #ejabberd_commands{name = muc_get_registered_nicks, tags = [muc],
187
                       desc = "List all nicks registered in the MUC service",
188
                       module = ?MODULE, function = muc_get_registered_nicks,
189
                       note = "added in 25.10",
190
                       args_desc = ["MUC service"],
191
                       args_example = [<<"conference.example.org">>],
192
                       args = [{service, binary}],
193
                       result_example = [{"Tim", "timexa", "example.com"},
194
                                         {"Laia", "laia001", "example2.org"}],
195
                       result = {registrations, {list, {registration, {tuple,
196
                                                          [{user, string},
197
                                                           {host, string},
198
                                                           {nick, string}
199
                                                          ]}}}}},
200

201
     #ejabberd_commands{name = create_room, tags = [muc_room],
202
                       desc = "Create a MUC room name@service in host",
203
                       module = ?MODULE, function = create_room,
204
                       args_desc = ["Room name", "MUC service", "Server host"],
205
                       args_example = ["room1", "conference.example.com", "example.com"],
206
                       args = [{room, binary}, {service, binary},
207
                               {host, binary}],
208
                       args_rename = [{name, room}],
209
                       result = {res, rescode}},
210
     #ejabberd_commands{name = destroy_room, tags = [muc_room],
211
                       desc = "Destroy a MUC room",
212
                       module = ?MODULE, function = destroy_room,
213
                       args_desc = ["Room name", "MUC service"],
214
                       args_example = ["room1", "conference.example.com"],
215
                       args = [{room, binary}, {service, binary}],
216
                       args_rename = [{name, room}],
217
                       result = {res, rescode}},
218
     #ejabberd_commands{name = create_rooms_file, tags = [muc],
219
                       desc = "Create the rooms indicated in file",
220
                       longdesc = "Provide one room JID per line. Rooms will be created after restart.",
221
                       note = "improved in 24.12",
222
                       module = ?MODULE, function = create_rooms_file,
223
                       args_desc = ["Path to the text file with one room JID per line"],
224
                       args_example = ["/home/ejabberd/rooms.txt"],
225
                       args = [{file, string}],
226
                       result = {res, rescode}},
227
     #ejabberd_commands{name = create_room_with_opts, tags = [muc_room, muc_sub],
228
                       desc = "Create a MUC room name@service in host with given options",
229
                       longdesc =
230
                        "Options `affiliations` and `subscribers` are lists of tuples. "
231
                        "The tuples in the list are separated with `;` and "
232
                        "the elements in each tuple are separated with `=` "
233
                        "(until ejabberd 24.12 the separators were `,` and `:` respectively). "
234
                        "Each subscriber can have one or more nodes. "
235
                        "In summary, `affiliations` is like `Type1=JID1;Type2=JID2` "
236
                        "and `subscribers` is like `JID1=Nick1=Node1A=Node1B=Node1C;JID2=Nick2=Node2`.",
237
                       note = "modified in 25.03",
238
                       module = ?MODULE, function = create_room_with_opts,
239
                       args_desc = ["Room name", "MUC service", "Server host", "List of options"],
240
                       args_example = ["room1", "conference.example.com", "localhost",
241
                                       [{"members_only","true"},
242
                                        {"affiliations", "owner=user1@localhost;member=user2@localhost"},
243
                                        {"subscribers", "user3@localhost=User3=messages=subject;user4@localhost=User4=messages"}]],
244
                       args = [{room, binary}, {service, binary},
245
                               {host, binary},
246
                               {options, {list,
247
                                          {option, {tuple,
248
                                                    [{name, binary},
249
                                                     {value, binary}
250
                                                    ]}}
251
                                         }}],
252
                       args_rename = [{name, room}],
253
                       result = {res, rescode}},
254
     #ejabberd_commands{name = destroy_rooms_file, tags = [muc],
255
                       desc = "Destroy the rooms indicated in file",
256
                       longdesc = "Provide one room JID per line.",
257
                       module = ?MODULE, function = destroy_rooms_file,
258
                       args_desc = ["Path to the text file with one room JID per line"],
259
                       args_example = ["/home/ejabberd/rooms.txt"],
260
                       args = [{file, string}],
261
                       result = {res, rescode}},
262
     #ejabberd_commands{name = rooms_unused_list, tags = [muc],
263
                       desc = "List the rooms that are unused for many days in the service",
264
                       longdesc = "The room recent history is used, so it's recommended "
265
                            " to wait a few days after service start before running this."
266
                            " The MUC service argument can be `global` to get all hosts.",
267
                       module = ?MODULE, function = rooms_unused_list,
268
                       args_desc = ["MUC service, or `global` for all", "Number of days"],
269
                       args_example = ["conference.example.com", 31],
270
                       result_desc = "List of unused rooms",
271
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
272
                       args = [{service, binary}, {days, integer}],
273
                       args_rename = [{host, service}],
274
                       result = {rooms, {list, {room, string}}}},
275
     #ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
276
                       desc = "Destroy the rooms that are unused for many days in the service",
277
                       longdesc = "The room recent history is used, so it's recommended "
278
                            " to wait a few days after service start before running this."
279
                            " The MUC service argument can be `global` to get all hosts.",
280
                       module = ?MODULE, function = rooms_unused_destroy,
281
                       args_desc = ["MUC service, or `global` for all", "Number of days"],
282
                       args_example = ["conference.example.com", 31],
283
                       result_desc = "List of unused rooms that has been destroyed",
284
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
285
                       args = [{service, binary}, {days, integer}],
286
                       args_rename = [{host, service}],
287
                       result = {rooms, {list, {room, string}}}},
288

289
     #ejabberd_commands{name = rooms_empty_list, tags = [muc],
290
                       desc = "List the rooms that have no messages in archive",
291
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
292
                       module = ?MODULE, function = rooms_empty_list,
293
                       args_desc = ["MUC service, or `global` for all"],
294
                       args_example = ["conference.example.com"],
295
                       result_desc = "List of empty rooms",
296
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
297
                       args = [{service, binary}],
298
                       args_rename = [{host, service}],
299
                       result = {rooms, {list, {room, string}}}},
300
     #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
301
                       desc = "Destroy the rooms that have no messages in archive",
302
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
303
                       module = ?MODULE, function = rooms_empty_destroy,
304
                       args_desc = ["MUC service, or `global` for all"],
305
                       args_example = ["conference.example.com"],
306
                       result_desc = "List of empty rooms that have been destroyed",
307
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
308
                       args = [{service, binary}],
309
                       args_rename = [{host, service}],
310
                       result = {rooms, {list, {room, string}}}},
311
     #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
312
                       desc = "Destroy the rooms that have no messages in archive",
313
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
314
                       module = ?MODULE, function = rooms_empty_destroy_restuple,
315
                       version = 2,
316
                       note = "modified in 24.06",
317
                       args_desc = ["MUC service, or `global` for all"],
318
                       args_example = ["conference.example.com"],
319
                       result_desc = "List of empty rooms that have been destroyed",
320
                       result_example = {ok, <<"Destroyed rooms: 2">>},
321
                       args = [{service, binary}],
322
                       args_rename = [{host, service}],
323
                       result = {res, restuple}},
324

325
     #ejabberd_commands{name = get_user_rooms, tags = [muc],
326
                        desc = "Get the list of rooms where this user is occupant",
327
                        module = ?MODULE, function = get_user_rooms,
328
                        args_desc = ["Username", "Server host"],
329
                        args_example = ["tom", "example.com"],
330
                        result_example = ["room1@conference.example.com", "room2@conference.example.com"],
331
                        args = [{user, binary}, {host, binary}],
332
                        result = {rooms, {list, {room, string}}}},
333
     #ejabberd_commands{name = get_user_subscriptions, tags = [muc, muc_sub],
334
                        desc = "Get the list of rooms where this user is subscribed",
335
                        note = "added in 21.04",
336
                        module = ?MODULE, function = get_user_subscriptions,
337
                        args_desc = ["Username", "Server host"],
338
                        args_example = ["tom", "example.com"],
339
                        result_example = [{"room1@conference.example.com", "Tommy", ["mucsub:config"]}],
340
                        args = [{user, binary}, {host, binary}],
341
                        result = {rooms,
342
                                  {list,
343
                                   {room,
344
                                    {tuple,
345
                                     [{roomjid, string},
346
                                      {usernick, string},
347
                                      {nodes, {list, {node, string}}}
348
                                     ]}}
349
                                  }}},
350

351
     #ejabberd_commands{name = get_room_occupants, tags = [muc_room],
352
                        desc = "Get the list of occupants of a MUC room",
353
                        module = ?MODULE, function = get_room_occupants,
354
                        args_desc = ["Room name", "MUC service"],
355
                        args_example = ["room1", "conference.example.com"],
356
                        result_desc = "The list of occupants with JID, nick and affiliation",
357
                        result_example = [{"user1@example.com/psi", "User 1", "owner"}],
358
                        args = [{room, binary}, {service, binary}],
359
                        args_rename = [{name, room}],
360
                        result = {occupants, {list,
361
                                              {occupant, {tuple,
362
                                                          [{jid, string},
363
                                                           {nick, string},
364
                                                           {role, string}
365
                                                          ]}}
366
                                             }}},
367

368
     #ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
369
                        desc = "Get the number of occupants of a MUC room",
370
                        module = ?MODULE, function = get_room_occupants_number,
371
                        args_desc = ["Room name", "MUC service"],
372
                        args_example = ["room1", "conference.example.com"],
373
                        result_desc = "Number of room occupants",
374
                        result_example = 7,
375
                        args = [{room, binary}, {service, binary}],
376
                        args_rename = [{name, room}],
377
                        result = {occupants, integer}},
378

379
     #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
380
                        desc = "Send a direct invitation to several destinations",
381
                        longdesc = "Since ejabberd 20.12, this command is "
382
                        "asynchronous: the API call may return before the "
383
                        "server has send all the invitations.\n\n"
384
                        "Password and Message can also be: `none`. "
385
                        "Users JIDs are separated with `:`.",
386
                        module = ?MODULE, function = send_direct_invitation,
387
                        args_desc = ["Room name", "MUC service", "Password, or `none`",
388
                         "Reason text, or `none`", "Users JIDs separated with `:` characters"],
389
                        args_example = [<<"room1">>, <<"conference.example.com">>,
390
                                        <<>>, <<"Check this out!">>,
391
                                        "user2@localhost:user3@example.com"],
392
                        args = [{room, binary}, {service, binary}, {password, binary},
393
                                {reason, binary}, {users, binary}],
394
                        args_rename = [{name, room}],
395
                        result = {res, rescode}},
396
     #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
397
                        desc = "Send a direct invitation to several destinations",
398
                        longdesc = "Since ejabberd 20.12, this command is "
399
                        "asynchronous: the API call may return before the "
400
                        "server has send all the invitations.\n\n"
401
                        "`password` and `message` can be set to `none`.",
402
                        module = ?MODULE, function = send_direct_invitation,
403
                        version = 1,
404
                        note = "updated in 24.02",
405
                        args_desc = ["Room name", "MUC service", "Password, or `none`",
406
                         "Reason text, or `none`", "List of users JIDs"],
407
                        args_example = [<<"room1">>, <<"conference.example.com">>,
408
                                        <<>>, <<"Check this out!">>,
409
                                        ["user2@localhost", "user3@example.com"]],
410
                        args = [{room, binary}, {service, binary}, {password, binary},
411
                                {reason, binary}, {users, {list, {jid, binary}}}],
412
                        args_rename = [{name, room}],
413
                        result = {res, rescode}},
414

415
     #ejabberd_commands{name = change_room_option, tags = [muc_room],
416
                       desc = "Change an option in a MUC room",
417
                       module = ?MODULE, function = change_room_option,
418
                       args_desc = ["Room name", "MUC service", "Option name", "Value to assign"],
419
                       args_example = ["room1", "conference.example.com", "members_only", "true"],
420
                       args = [{room, binary}, {service, binary},
421
                               {option, binary}, {value, binary}],
422
                       args_rename = [{name, room}],
423
                       result = {res, rescode}},
424
     #ejabberd_commands{name = get_room_options, tags = [muc_room],
425
                        desc = "Get options from a MUC room",
426
                        module = ?MODULE, function = get_room_options,
427
                        args_desc = ["Room name", "MUC service"],
428
                        args_example = ["room1", "conference.example.com"],
429
                        result_desc = "List of room options tuples with name and value",
430
                        result_example = [{"members_only", "true"}],
431
                        args = [{room, binary}, {service, binary}],
432
                        args_rename = [{name, room}],
433
                        result = {options, {list,
434
                                                 {option, {tuple,
435
                                                                [{name, string},
436
                                                                 {value, string}
437
                                                                ]}}
438
                                                }}},
439
     #ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
440
                        desc = "Subscribe to a MUC conference",
441
                        module = ?MODULE, function = subscribe_room,
442
                        args_desc = ["User JID", "a user's nick",
443
                            "the room to subscribe", "nodes separated by commas: `,`"],
444
                        args_example = ["tom@localhost", "Tom", "room1@conference.localhost",
445
                            "urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
446
                        result_desc = "The list of nodes that has subscribed",
447
                        result_example = ["urn:xmpp:mucsub:nodes:messages",
448
                            "urn:xmpp:mucsub:nodes:affiliations"],
449
                        args = [{user, binary}, {nick, binary}, {room, binary},
450
                                {nodes, binary}],
451
                        result = {nodes, {list, {node, string}}}},
452
     #ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
453
                        desc = "Subscribe to a MUC conference",
454
                        module = ?MODULE, function = subscribe_room,
455
                        version = 1,
456
                        note = "updated in 24.02",
457
                        args_desc = ["User JID", "a user's nick",
458
                            "the room to subscribe", "list of nodes"],
459
                        args_example = ["tom@localhost", "Tom", "room1@conference.localhost",
460
                            ["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
461
                        result_desc = "The list of nodes that has subscribed",
462
                        result_example = ["urn:xmpp:mucsub:nodes:messages",
463
                            "urn:xmpp:mucsub:nodes:affiliations"],
464
                        args = [{user, binary}, {nick, binary}, {room, binary},
465
                                {nodes, {list, {node, binary}}}],
466
                        result = {nodes, {list, {node, string}}}},
467
     #ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
468
                        desc = "Subscribe to a MUC conference",
469
                        module = ?MODULE, function = subscribe_room,
470
                        version = 3,
471
                        note = "updated in 24.12",
472
                        args_desc = ["user name", "user host", "user nick",
473
                            "room name", "MUC service", "list of nodes"],
474
                        args_example = ["tom", "localhost", "Tom", "room1", "conference.localhost",
475
                            ["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
476
                        result_desc = "The list of nodes that has subscribed",
477
                        result_example = ["urn:xmpp:mucsub:nodes:messages",
478
                            "urn:xmpp:mucsub:nodes:affiliations"],
479
                        args = [{user, binary}, {host, binary}, {nick, binary}, {room, binary},
480
                                {service, binary}, {nodes, {list, {node, binary}}}],
481
                        result = {nodes, {list, {node, string}}}},
482
     #ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
483
                        desc = "Subscribe several users to a MUC conference",
484
                        note = "added in 22.05",
485
                        longdesc = "This command accepts up to 50 users at once "
486
                            "(this is configurable with the _`mod_muc_admin`_ option "
487
                            "`subscribe_room_many_max_users`)",
488
                        module = ?MODULE, function = subscribe_room_many,
489
                        args_desc = ["Users JIDs and nicks",
490
                                     "the room to subscribe",
491
                                     "nodes separated by commas: `,`"],
492
                        args_example = [[{"tom@localhost", "Tom"},
493
                                         {"jerry@localhost", "Jerry"}],
494
                                        "room1@conference.localhost",
495
                                        "urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
496
                        args = [{users, {list,
497
                                         {user, {tuple,
498
                                                 [{jid, binary},
499
                                                  {nick, binary}
500
                                                 ]}}
501
                                        }},
502
                                {room, binary},
503
                                {nodes, binary}],
504
                        result = {res, rescode}},
505
     #ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
506
                        desc = "Subscribe several users to a MUC conference",
507
                        longdesc = "This command accepts up to 50 users at once "
508
                            "(this is configurable with the _`mod_muc_admin`_ option "
509
                            "`subscribe_room_many_max_users`)",
510
                        module = ?MODULE, function = subscribe_room_many,
511
                        version = 1,
512
                        note = "updated in 24.02",
513
                        args_desc = ["Users JIDs and nicks",
514
                                     "the room to subscribe",
515
                                     "nodes separated by commas: `,`"],
516
                        args_example = [[{"tom@localhost", "Tom"},
517
                                         {"jerry@localhost", "Jerry"}],
518
                                        "room1@conference.localhost",
519
                                        ["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
520
                        args = [{users, {list,
521
                                         {user, {tuple,
522
                                                 [{jid, binary},
523
                                                  {nick, binary}
524
                                                 ]}}
525
                                        }},
526
                                {room, binary},
527
                                {nodes, {list, {node, binary}}}],
528
                        result = {res, rescode}},
529
     #ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
530
                        desc = "Subscribe several users to a MUC conference",
531
                        longdesc = "This command accepts up to 50 users at once "
532
                            "(this is configurable with the _`mod_muc_admin`_ option "
533
                            "`subscribe_room_many_max_users`)",
534
                        module = ?MODULE, function = subscribe_room_many_v3,
535
                        version = 3,
536
                        note = "updated in 24.12",
537
                        args_desc = ["List of tuples with users name, host and nick",
538
                                     "room name",
539
                                     "MUC service",
540
                                     "nodes separated by commas: `,`"],
541
                        args_example = [[{"tom", "localhost", "Tom"},
542
                                         {"jerry", "localhost", "Jerry"}],
543
                                        "room1", "conference.localhost",
544
                                        ["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
545
                        args = [{users, {list,
546
                                         {user, {tuple,
547
                                                 [{user, binary},
548
                                                  {host, binary},
549
                                                  {nick, binary}
550
                                                 ]}}
551
                                        }},
552
                                {room, binary}, {service, binary},
553
                                {nodes, {list, {node, binary}}}],
554
                        result = {res, rescode}},
555
     #ejabberd_commands{name = unsubscribe_room, tags = [muc_room, muc_sub],
556
                        desc = "Unsubscribe from a MUC conference",
557
                        module = ?MODULE, function = unsubscribe_room,
558
                        args_desc = ["User JID", "the room to subscribe"],
559
                        args_example = ["tom@localhost", "room1@conference.localhost"],
560
                        args = [{user, binary}, {room, binary}],
561
                        result = {res, rescode}},
562
     #ejabberd_commands{name = unsubscribe_room, tags = [muc_room, muc_sub],
563
                        desc = "Unsubscribe from a MUC conference",
564
                        module = ?MODULE, function = unsubscribe_room,
565
                        version = 3,
566
                        note = "updated in 24.12",
567
                        args_desc = ["user name", "user host", "room name", "MUC service"],
568
                        args_example = ["tom", "localhost", "room1", "conference.localhost"],
569
                        args = [{user, binary}, {host, binary}, {room, binary}, {service, binary}],
570
                        result = {res, rescode}},
571
     #ejabberd_commands{name = get_subscribers, tags = [muc_room, muc_sub],
572
                        desc = "List subscribers of a MUC conference",
573
                        module = ?MODULE, function = get_subscribers,
574
                        args_desc = ["Room name", "MUC service"],
575
                        args_example = ["room1", "conference.example.com"],
576
                        result_desc = "The list of users that are subscribed to that room",
577
                        result_example = ["user2@example.com", "user3@example.com"],
578
                        args = [{room, binary}, {service, binary}],
579
                        args_rename = [{name, room}],
580
                        result = {subscribers, {list, {jid, string}}}},
581
     #ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
582
                       desc = "Change an affiliation in a MUC room",
583
                       module = ?MODULE, function = set_room_affiliation,
584
                       args_desc = ["Room name", "MUC service", "User JID", "Affiliation to set"],
585
                       args_example = ["room1", "conference.example.com", "user2@example.com", "member"],
586
                       args = [{name, binary}, {service, binary},
587
                               {jid, binary}, {affiliation, binary}],
588
                       result = {res, rescode}},
589
     #ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
590
                       desc = "Change an affiliation in a MUC room",
591
                       longdesc = "If affiliation is `none`, then the affiliation is removed.",
592
                       module = ?MODULE, function = set_room_affiliation,
593
                       version = 3,
594
                       note = "updated in 24.12",
595
                       args_desc = ["room name", "MUC service", "user name", "user host", "affiliation to set"],
596
                       args_example = ["room1", "conference.example.com", "sun", "localhost", "member"],
597
                       args = [{room, binary}, {service, binary},
598
                               {user, binary}, {host, binary}, {affiliation, binary}],
599
                       result = {res, rescode}},
600

601

602
     #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
603
                        desc = "Get the list of affiliations of a MUC room",
604
                        module = ?MODULE, function = get_room_affiliations,
605
                        args_desc = ["Room name", "MUC service"],
606
                        args_example = ["room1", "conference.example.com"],
607
                        result_desc = "The list of affiliations with username, domain, affiliation and reason",
608
                        result_example = [{"user1", "example.com", member, "member"}],
609
                        args = [{name, binary}, {service, binary}],
610
                        result = {affiliations, {list,
611
                                                 {affiliation, {tuple,
612
                                                                [{username, string},
613
                                                                 {domain, string},
614
                                                                 {affiliation, atom},
615
                                                                 {reason, string}
616
                                                                ]}}
617
                                                }}},
618
     #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
619
                        desc = "Get the list of affiliations of a MUC room",
620
                        module = ?MODULE, function = get_room_affiliations_v3,
621
                        version = 3,
622
                        note = "updated in 24.12",
623
                        args_desc = ["Room name", "MUC service"],
624
                        args_example = ["room1", "conference.example.com"],
625
                        result_desc = "The list of affiliations with jid, affiliation and reason",
626
                        result_example = [{"user1@example.com", member, "member"}],
627
                        args = [{room, binary}, {service, binary}],
628
                        result = {affiliations, {list,
629
                                                 {affiliation, {tuple,
630
                                                                [{jid, string},
631
                                                                 {affiliation, atom},
632
                                                                 {reason, string}
633
                                                                ]}}
634
                                                }}},
635

636

637
         #ejabberd_commands{name = get_room_affiliation, tags = [muc_room],
638
                        desc = "Get affiliation of a user in MUC room",
639
                        module = ?MODULE, function = get_room_affiliation,
640
                        args_desc = ["Room name", "MUC service", "User JID"],
641
                        args_example = ["room1", "conference.example.com", "user1@example.com"],
642
                        result_desc = "Affiliation of the user",
643
                        result_example = member,
644
                        args = [{room, binary}, {service, binary}, {jid, binary}],
645
                        args_rename = [{name, room}],
646
                        result = {affiliation, atom}},
647
         #ejabberd_commands{name = get_room_history, tags = [muc_room],
648
                        desc = "Get history of messages stored inside MUC room state",
649
                        note = "added in 23.04",
650
                        module = ?MODULE, function = get_room_history,
651
                        args_desc = ["Room name", "MUC service"],
652
                        args_example = ["room1", "conference.example.com"],
653
                        args = [{room, binary}, {service, binary}],
654
                        args_rename = [{name, room}],
655
                        result = {history, {list,
656
                                            {entry, {tuple,
657
                                                     [{timestamp, string},
658
                                                      {message, string}]}}}}},
659

660
         #ejabberd_commands{name = webadmin_muc, tags = [internal],
661
                        desc = "Generate WebAdmin MUC Rooms HTML",
662
                        module = ?MODULE, function = webadmin_muc,
663
                        args = [{request, any}, {lang, binary}],
664
                        result = {res, any}}
665
        ].
666

667

668
%%%
669
%%% ejabberd commands
670
%%%
671

672
muc_online_rooms(ServiceArg) ->
673
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
674
    lists:flatmap(
×
675
      fun(Host) ->
676
              [<<Name/binary, "@", Host/binary>>
×
677
               || {Name, _, _} <- mod_muc:get_online_rooms(Host)]
×
678
      end, Hosts).
679

680
muc_online_rooms_count(ServiceArg) ->
681
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
682
    lists:foldl(
×
683
                fun(Host, Acc) ->
684
                        Acc+mod_muc:count_online_rooms(Host)
×
685
                end, 0, Hosts).
686

687
muc_online_rooms_by_regex(ServiceArg, Regex) ->
688
    {_, P} = re:compile(Regex),
×
689
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
690
    lists:flatmap(
×
691
      fun(Host) ->
692
              [build_summary_room(Name, RoomHost, Pid)
×
693
               || {Name, RoomHost, Pid} <- mod_muc:get_online_rooms(Host),
×
694
                   is_name_match(Name, P)]
×
695
      end, Hosts).
696

697
is_name_match(Name, P) ->
698
        case re:run(Name, P) of
×
699
                {match, _} -> true;
×
700
                nomatch -> false
×
701
        end.
702

703
build_summary_room(Name, Host, Pid) ->
704
    C = get_room_config(Pid),
×
705
    Public = C#config.public,
×
706
    S = get_room_state(Pid),
×
707
    Participants = maps:size(S#state.users),
×
708
    {<<Name/binary, "@", Host/binary>>,
×
709
         misc:atom_to_binary(Public),
710
     Participants
711
    }.
712

713
muc_register_nick(Nick, User, Host, Service) ->
714
    muc_register_nick(Nick, makeencode(User, Host), Service).
×
715

716
muc_register_nick(Nick, FromBinary, Service) ->
717
    try {get_room_serverhost(Service), jid:decode(FromBinary)} of
×
718
        {ServerHost, From} ->
719
            Lang = <<"en">>,
×
720
            case mod_muc:iq_set_register_info(ServerHost, Service, From, Nick, Lang) of
×
721
                {result, undefined} -> ok;
×
722
                {error, #stanza_error{reason = 'conflict'}} ->
723
                    throw({error, "Nick already registered"});
×
724
                {error, _} ->
725
                    throw({error, "Database error"})
×
726
            end
727
        catch
728
        error:{invalid_domain, _} ->
729
            throw({error, "Invalid value of 'service'"});
×
730
        error:{unregistered_route, _} ->
731
            throw({error, "Unknown host in 'service'"});
×
732
        error:{bad_jid, _} ->
733
            throw({error, "Invalid 'jid'"});
×
734
        _ ->
735
            throw({error, "Internal error"})
×
736
    end.
737

738
muc_unregister_nick(User, Host, Service) ->
739
    muc_unregister_nick(makeencode(User, Host), Service).
×
740

741
muc_unregister_nick(FromBinary, Service) ->
742
    muc_register_nick(<<"">>, FromBinary, Service).
×
743

744
muc_get_registered_nick(User, Host, Service) ->
745
    MucServerHost = get_room_serverhost(Service),
×
746
    case mod_muc:get_register_nick(MucServerHost, Service, jid:make(User, Host)) of
×
747
        error -> <<"">>;
×
748
        N -> N
×
749
    end.
750

751
muc_get_registered_nicks(Service) ->
752
    MucServerHost = get_room_serverhost(Service),
×
753
    mod_muc:get_register_nicks(MucServerHost, Service).
×
754

755
get_user_rooms(User, Server) ->
756
    lists:flatmap(
×
757
      fun(ServerHost) ->
758
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
759
                  true ->
760
                      Rooms = mod_muc:get_online_rooms_by_user(
×
761
                                ServerHost, jid:nodeprep(User), jid:nodeprep(Server)),
762
                      [<<Name/binary, "@", Host/binary>>
×
763
                           || {Name, Host} <- Rooms];
×
764
                  false ->
765
                      []
×
766
              end
767
      end, ejabberd_option:hosts()).
768

769
get_user_subscriptions(User, Server) ->
770
    User2 = validate_user(User, <<"user">>),
×
771
    Server2 = validate_host(Server, <<"host">>),
×
772
    Services = find_services(global),
×
773
    UserJid = jid:make(User2, Server2),
×
774
    lists:flatmap(
×
775
      fun(ServerHost) ->
776
              {ok, Rooms} = mod_muc:get_subscribed_rooms(ServerHost, UserJid),
×
777
              [{jid:encode(RoomJid), UserNick, Nodes}
×
778
               || {RoomJid, UserNick, Nodes} <- Rooms]
×
779
      end, Services).
780

781
%%----------------------------
782
%% Ad-hoc commands
783
%%----------------------------
784

785

786
%%----------------------------
787
%% Web Admin
788
%%----------------------------
789

790
%% @format-begin
791

792
%%---------------
793
%% Web Admin Menu
794

795
web_menu_main(Acc, Lang) ->
UNCOV
796
    Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}].
48✔
797

798
web_menu_host(Acc, _Host, Lang) ->
799
    Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}].
×
800

801
%%---------------
802
%% Web Admin Page
803

804
web_page_main(_, #request{path = [<<"muc">>], lang = Lang} = R) ->
805
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
806
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
807
    Res = [make_command(webadmin_muc, R, [{<<"request">>, R}, {<<"lang">>, Lang}], [])],
×
808
    {stop, Title ++ Res};
×
809
web_page_main(Acc, _) ->
810
    Acc.
×
811

812
web_page_host(_, Host, #request{path = [<<"muc">> | RPath], lang = Lang} = R) ->
813
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
814
    Service = find_service(Host),
×
815
    Level = length(RPath),
×
816
    Res = webadmin_muc_host(Host, Service, RPath, R, Lang, Level, PageTitle),
×
817
    {stop, Res};
×
818
web_page_host(Acc, _, _) ->
819
    Acc.
×
820

821
%%---------------
822
%% WebAdmin MUC Host Page
823

824
webadmin_muc_host(Host,
825
                  Service,
826
                  [<<"create-room">> | RPath],
827
                  R,
828
                  _Lang,
829
                  Level,
830
                  PageTitle) ->
831
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
832
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Create Room">>, RPath}),
×
833
    Set = [make_command(create_room, R, [{<<"service">>, Service}, {<<"host">>, Host}], []),
×
834
           make_command(create_room_with_opts,
835
                        R,
836
                        [{<<"service">>, Service}, {<<"host">>, Host}],
837
                        [])],
838
    Title ++ Breadcrumb ++ Set;
×
839
webadmin_muc_host(_Host,
840
                  Service,
841
                  [<<"nick-register">> | RPath],
842
                  R,
843
                  _Lang,
844
                  Level,
845
                  PageTitle) ->
846
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
847
    Breadcrumb =
×
848
        make_breadcrumb({service_section, Level, Service, <<"Nick Register">>, RPath}),
849

850
    Set = [make_command(muc_register_nick, R, [{<<"service">>, Service}], [])],
×
851
    %% Execute twice: first to perform the action, the second to get new roster
852
    _ = make_webadmin_roster_table(Service, R, RPath),
×
853
    RV2 = make_webadmin_roster_table(Service, R, RPath),
×
854
    Get = [make_command(muc_get_registered_nicks,
×
855
                        R,
856
                        [{<<"service">>, Service}],
857
                        [{only, presentation}]),
858
           ?XE(<<"blockquote">>,
859
               [make_command(muc_unregister_nick,
860
                             R,
861
                             [{<<"service">>, Service}],
862
                             [{only, presentation}])]),
863
           RV2],
864
    Title ++ Breadcrumb ++ Set ++ Get;
×
865
webadmin_muc_host(_Host,
866
                  Service,
867
                  [<<"rooms-empty">> | RPath],
868
                  R,
869
                  _Lang,
870
                  Level,
871
                  PageTitle) ->
872
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
873
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms Empty">>, RPath}),
×
874
    Set = [make_command(rooms_empty_list,
×
875
                        R,
876
                        [{<<"service">>, Service}],
877
                        [{table_options, {2, RPath}},
878
                         {result_links, [{room, room, 3 + Level, <<"">>}]}]),
879
           make_command(rooms_empty_destroy, R, [{<<"service">>, Service}], [])],
880
    Title ++ Breadcrumb ++ Set;
×
881
webadmin_muc_host(_Host,
882
                  Service,
883
                  [<<"rooms-unused">> | RPath],
884
                  R,
885
                  _Lang,
886
                  Level,
887
                  PageTitle) ->
888
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
889
    Breadcrumb =
×
890
        make_breadcrumb({service_section, Level, Service, <<"Rooms Unused">>, RPath}),
891
    Set = [make_command(rooms_unused_list,
×
892
                        R,
893
                        [{<<"service">>, Service}],
894
                        [{result_links, [{room, room, 3 + Level, <<"">>}]}]),
895
           make_command(rooms_unused_destroy, R, [{<<"service">>, Service}], [])],
896
    Title ++ Breadcrumb ++ Set;
×
897
webadmin_muc_host(_Host,
898
                  Service,
899
                  [<<"rooms-regex">> | RPath],
900
                  R,
901
                  _Lang,
902
                  Level,
903
                  PageTitle) ->
904
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
905
    Breadcrumb =
×
906
        make_breadcrumb({service_section, Level, Service, <<"Rooms by Regex">>, RPath}),
907
    Set = [make_command(muc_online_rooms_by_regex,
×
908
                        R,
909
                        [{<<"service">>, Service}],
910
                        [{result_links, [{jid, room, 3 + Level, <<"">>}]}])],
911
    Title ++ Breadcrumb ++ Set;
×
912
webadmin_muc_host(_Host,
913
                  Service,
914
                  [<<"rooms">>, <<"room">>, Name, <<"affiliations">> | RPath],
915
                  R,
916
                  _Lang,
917
                  Level,
918
                  PageTitle) ->
919
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
920
    Breadcrumb =
×
921
        make_breadcrumb({room_section, Level, Service, <<"Affiliations">>, Name, R, RPath}),
922
    Set = [make_command(set_room_affiliation,
×
923
                        R,
924
                        [{<<"room">>, Name}, {<<"service">>, Service}],
925
                        [])],
926
    Get = [make_command(get_room_affiliations,
×
927
                        R,
928
                        [{<<"room">>, Name}, {<<"service">>, Service}],
929
                        [{table_options, {20, RPath}},
930
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
931
    Title ++ Breadcrumb ++ Get ++ Set;
×
932
webadmin_muc_host(_Host,
933
                  Service,
934
                  [<<"rooms">>, <<"room">>, Name, <<"history">> | RPath],
935
                  R,
936
                  _Lang,
937
                  Level,
938
                  PageTitle) ->
939
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
940
    Breadcrumb =
×
941
        make_breadcrumb({room_section, Level, Service, <<"History">>, Name, R, RPath}),
942
    Get = [make_command(get_room_history,
×
943
                        R,
944
                        [{<<"room">>, Name}, {<<"service">>, Service}],
945
                        [{table_options, {10, RPath}},
946
                         {result_links, [{message, paragraph, 1, <<"">>}]}])],
947
    Title ++ Breadcrumb ++ Get;
×
948
webadmin_muc_host(_Host,
949
                  Service,
950
                  [<<"rooms">>, <<"room">>, Name, <<"invite">> | RPath],
951
                  R,
952
                  _Lang,
953
                  Level,
954
                  PageTitle) ->
955
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
956
    Breadcrumb =
×
957
        make_breadcrumb({room_section, Level, Service, <<"Invite">>, Name, R, RPath}),
958
    Set = [make_command(send_direct_invitation,
×
959
                        R,
960
                        [{<<"room">>, Name}, {<<"service">>, Service}],
961
                        [])],
962
    Title ++ Breadcrumb ++ Set;
×
963
webadmin_muc_host(_Host,
964
                  Service,
965
                  [<<"rooms">>, <<"room">>, Name, <<"occupants">> | RPath],
966
                  R,
967
                  _Lang,
968
                  Level,
969
                  PageTitle) ->
970
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
971
    Breadcrumb =
×
972
        make_breadcrumb({room_section, Level, Service, <<"Occupants">>, Name, R, RPath}),
973
    Get = [make_command(get_room_occupants,
×
974
                        R,
975
                        [{<<"room">>, Name}, {<<"service">>, Service}],
976
                        [{table_options, {20, RPath}},
977
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
978
    Title ++ Breadcrumb ++ Get;
×
979
webadmin_muc_host(_Host,
980
                  Service,
981
                  [<<"rooms">>, <<"room">>, Name, <<"options">> | RPath],
982
                  R,
983
                  _Lang,
984
                  Level,
985
                  PageTitle) ->
986
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
987
    Breadcrumb =
×
988
        make_breadcrumb({room_section, Level, Service, <<"Options">>, Name, R, RPath}),
989
    Set = [make_command(change_room_option,
×
990
                        R,
991
                        [{<<"room">>, Name}, {<<"service">>, Service}],
992
                        [])],
993
    Get = [make_command(get_room_options,
×
994
                        R,
995
                        [{<<"room">>, Name}, {<<"service">>, Service}],
996
                        [])],
997
    Title ++ Breadcrumb ++ Get ++ Set;
×
998
webadmin_muc_host(_Host,
999
                  Service,
1000
                  [<<"rooms">>, <<"room">>, Name, <<"subscribers">> | RPath],
1001
                  R,
1002
                  _Lang,
1003
                  Level,
1004
                  PageTitle) ->
1005
    Title =
×
1006
        ?H1GLraw(PageTitle,
1007
                 <<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
1008
                 <<"MUC/Sub Extension">>),
1009
    Breadcrumb =
×
1010
        make_breadcrumb({room_section, Level, Service, <<"Subscribers">>, Name, R, RPath}),
1011
    Set = [make_command(subscribe_room,
×
1012
                        R,
1013
                        [{<<"room">>, Name}, {<<"service">>, Service}],
1014
                        []),
1015
           make_command(unsubscribe_room,
1016
                        R,
1017
                        [{<<"room">>, Name}, {<<"service">>, Service}],
1018
                        [{style, danger}])],
1019
    Get = [make_command(get_subscribers,
×
1020
                        R,
1021
                        [{<<"room">>, Name}, {<<"service">>, Service}],
1022
                        [{table_options, {20, RPath}},
1023
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
1024
    Title ++ Breadcrumb ++ Get ++ Set;
×
1025
webadmin_muc_host(_Host,
1026
                  Service,
1027
                  [<<"rooms">>, <<"room">>, Name, <<"destroy">> | RPath],
1028
                  R,
1029
                  _Lang,
1030
                  Level,
1031
                  PageTitle) ->
1032
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
1033
    Breadcrumb =
×
1034
        make_breadcrumb({room_section, Level, Service, <<"Destroy">>, Name, R, RPath}),
1035
    Set = [make_command(destroy_room,
×
1036
                        R,
1037
                        [{<<"room">>, Name}, {<<"service">>, Service}],
1038
                        [{style, danger}])],
1039
    Title ++ Breadcrumb ++ Set;
×
1040
webadmin_muc_host(_Host,
1041
                  Service,
1042
                  [<<"rooms">>, <<"room">>, Name | _RPath],
1043
                  _R,
1044
                  Lang,
1045
                  Level,
1046
                  PageTitle) ->
1047
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
1048
    Breadcrumb = make_breadcrumb({room, Level, Service, Name}),
×
1049
    MenuItems =
×
1050
        [{<<"affiliations/">>, <<"Affiliations">>},
1051
         {<<"history/">>, <<"History">>},
1052
         {<<"invite/">>, <<"Invite">>},
1053
         {<<"occupants/">>, <<"Occupants">>},
1054
         {<<"options/">>, <<"Options">>},
1055
         {<<"subscribers/">>, <<"Subscribers">>},
1056
         {<<"destroy/">>, <<"Destroy">>}],
1057
    Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
×
1058
    Title ++ Breadcrumb ++ Get;
×
1059
webadmin_muc_host(_Host, Service, [<<"rooms">> | RPath], R, _Lang, Level, PageTitle) ->
1060
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
1061
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms">>, RPath}),
×
1062
    Columns = [<<"jid">>, <<"occupants">>],
×
1063
    Rows =
×
1064
        lists:map(fun(NameService) ->
1065
                     #jid{user = Name} = jid:decode(NameService),
×
1066
                     {make_command(echo,
×
1067
                                   R,
1068
                                   [{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
1069
                                   [{only, raw_and_value},
1070
                                    {result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
1071
                      make_command(get_room_occupants_number,
1072
                                   R,
1073
                                   [{<<"room">>, Name}, {<<"service">>, Service}],
1074
                                   [{only, raw_and_value}])}
1075
                  end,
1076
                  make_command_raw_value(muc_online_rooms, R, [{<<"service">>, Service}])),
1077
    Get = [make_command(muc_online_rooms, R, [], [{only, presentation}]),
×
1078
           make_command(get_room_occupants_number, R, [], [{only, presentation}]),
1079
           make_table(20, RPath, Columns, Rows)],
1080
    Title ++ Breadcrumb ++ Get;
×
1081
webadmin_muc_host(_Host, Service, [], _R, Lang, _Level, PageTitle) ->
1082
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
1083
    Breadcrumb = make_breadcrumb({service, Service}),
×
1084
    MenuItems =
×
1085
        [{<<"create-room/">>, <<"Create Room">>},
1086
         {<<"rooms/">>, <<"Rooms">>},
1087
         {<<"rooms-regex/">>, <<"Rooms by Regex">>},
1088
         {<<"rooms-empty/">>, <<"Rooms Empty">>},
1089
         {<<"rooms-unused/">>, <<"Rooms Unused">>},
1090
         {<<"nick-register/">>, <<"Nick Register">>}],
1091
    Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
×
1092
    Title ++ Breadcrumb ++ Get;
×
1093
webadmin_muc_host(_Host, _Service, _RPath, _R, _Lang, _Level, _PageTitle) ->
1094
    [].
×
1095

1096
make_breadcrumb({service, Service}) ->
1097
    make_breadcrumb([Service]);
×
1098
make_breadcrumb({service_section, Level, Service, Section, RPath}) ->
1099
    make_breadcrumb([{Level, Service}, separator, Section | RPath]);
×
1100
make_breadcrumb({room, Level, Service, Name}) ->
1101
    make_breadcrumb([{Level, Service},
×
1102
                     separator,
1103
                     {Level - 1, <<"Rooms">>},
1104
                     separator,
1105
                     jid:encode({Name, Service, <<"">>})]);
1106
make_breadcrumb({room_section, Level, Service, Section, Name, R, RPath}) ->
1107
    make_breadcrumb([{Level, Service},
×
1108
                     separator,
1109
                     {Level - 1, <<"Rooms">>},
1110
                     separator,
1111
                     make_command(echo,
1112
                                  R,
1113
                                  [{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
1114
                                  [{only, value},
1115
                                   {result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
1116
                     separator,
1117
                     Section
1118
                     | RPath]);
1119
make_breadcrumb(Elements) ->
1120
    lists:map(fun ({xmlel, _, _, _} = Xmlel) ->
×
1121
                      Xmlel;
×
1122
                  (<<"sort">>) ->
1123
                      ?C(<<" +">>);
×
1124
                  (<<"page">>) ->
1125
                      ?C(<<" #">>);
×
1126
                  (separator) ->
1127
                      ?C(<<" > ">>);
×
1128
                  (Bin) when is_binary(Bin) ->
1129
                      ?C(Bin);
×
1130
                  ({Level, Bin}) when is_integer(Level) and is_binary(Bin) ->
1131
                      ?AC(binary:copy(<<"../">>, Level), Bin)
×
1132
              end,
1133
              Elements).
1134

1135
%%---------------
1136
%%
1137

1138
%% Returns: {normal | reverse, Integer}
1139
get_sort_query(Q) ->
1140
    case catch get_sort_query2(Q) of
×
1141
        {ok, Res} ->
1142
            Res;
×
1143
        _ ->
1144
            {normal, 1}
×
1145
    end.
1146

1147
get_sort_query2(Q) ->
1148
    {value, {_, Binary}} = lists:keysearch(<<"sort">>, 1, Q),
×
1149
    Integer = list_to_integer(string:strip(binary_to_list(Binary), right, $/)),
×
1150
    case Integer >= 0 of
×
1151
        true ->
1152
            {ok, {normal, Integer}};
×
1153
        false ->
1154
            {ok, {reverse, abs(Integer)}}
×
1155
    end.
1156

1157
webadmin_muc(#request{q = Q} = R, Lang) ->
1158
    {Sort_direction, Sort_column} = get_sort_query(Q),
×
1159
    Host = global,
×
1160
    Service = find_service(Host),
×
1161
    Rooms_names = get_online_rooms(Service),
×
1162
    Rooms_infos = build_info_rooms(Rooms_names),
×
1163
    Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
×
1164
    Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
×
1165
    TList =
×
1166
        lists:map(fun([RoomJid | Room]) ->
1167
                     JidLink =
×
1168
                         make_command(echo,
1169
                                      R,
1170
                                      [{<<"sentence">>, RoomJid}],
1171
                                      [{only, value},
1172
                                       {result_links, [{sentence, room, 1, <<"">>}]}]),
1173
                     ?XE(<<"tr">>, [?XE(<<"td">>, [JidLink]) | [?XC(<<"td">>, E) || E <- Room]])
×
1174
                  end,
1175
                  Rooms_prepared),
1176
    Titles =
×
1177
        [?T("Jabber ID"),
1178
         ?T("# participants"),
1179
         ?T("Last message"),
1180
         ?T("Public"),
1181
         ?T("Persistent"),
1182
         ?T("Logging"),
1183
         ?T("Just created"),
1184
         ?T("Room title"),
1185
         ?T("Node")],
1186
    {Titles_TR, _} =
×
1187
        lists:mapfoldl(fun(Title, Num_column) ->
1188
                          NCS = integer_to_binary(Num_column),
×
1189
                          TD = ?XE(<<"td">>,
×
1190
                                   [?CT(Title),
1191
                                    ?C(<<" ">>),
1192
                                    ?AC(<<"?sort=", NCS/binary>>, <<"<">>),
1193
                                    ?C(<<" ">>),
1194
                                    ?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
1195
                          {TD, Num_column + 1}
×
1196
                       end,
1197
                       1,
1198
                       Titles),
1199
    [?XCT(<<"h2">>, ?T("Chatrooms")),
×
1200
     ?XE(<<"table">>,
1201
         [?XE(<<"thead">>, [?XE(<<"tr">>, Titles_TR)]), ?XE(<<"tbody">>, TList)])].
1202

1203
sort_rooms(Direction, Column, Rooms) ->
1204
    Rooms2 = lists:keysort(Column, Rooms),
×
1205
    case Direction of
×
1206
        normal ->
1207
            Rooms2;
×
1208
        reverse ->
1209
            lists:reverse(Rooms2)
×
1210
    end.
1211

1212
build_info_rooms(Rooms) ->
1213
    [build_info_room(Room) || Room <- Rooms].
×
1214

1215
build_info_room({Name, Host, _ServerHost, Pid}) ->
1216
    C = get_room_config(Pid),
×
1217
    Title = C#config.title,
×
1218
    Public = C#config.public,
×
1219
    Persistent = C#config.persistent,
×
1220
    Logging = C#config.logging,
×
1221

1222
    S = get_room_state(Pid),
×
1223
    Just_created = S#state.just_created,
×
1224
    Num_participants = maps:size(S#state.users),
×
1225
    Node = node(Pid),
×
1226

1227
    History = S#state.history#lqueue.queue,
×
1228
    Ts_last_message =
×
1229
        case p1_queue:is_empty(History) of
1230
            true ->
1231
                <<"A long time ago">>;
×
1232
            false ->
1233
                Last_message1 = get_queue_last(History),
×
1234
                {_, _, _, Ts_last, _} = Last_message1,
×
1235
                xmpp_util:encode_timestamp(Ts_last)
×
1236
        end,
1237

1238
    {<<Name/binary, "@", Host/binary>>,
×
1239
     Num_participants,
1240
     Ts_last_message,
1241
     Public,
1242
     Persistent,
1243
     Logging,
1244
     Just_created,
1245
     Title,
1246
     Node}.
1247

1248
get_queue_last(Queue) ->
1249
    List = p1_queue:to_list(Queue),
×
1250
    lists:last(List).
×
1251

1252
prepare_rooms_infos(Rooms) ->
1253
    [prepare_room_info(Room) || Room <- Rooms].
×
1254

1255
prepare_room_info(Room_info) ->
1256
    {NameHost,
×
1257
     Num_participants,
1258
     Ts_last_message,
1259
     Public,
1260
     Persistent,
1261
     Logging,
1262
     Just_created,
1263
     Title,
1264
     Node} =
1265
        Room_info,
1266
    [NameHost,
×
1267
     integer_to_binary(Num_participants),
1268
     Ts_last_message,
1269
     misc:atom_to_binary(Public),
1270
     misc:atom_to_binary(Persistent),
1271
     misc:atom_to_binary(Logging),
1272
     justcreated_to_binary(Just_created),
1273
     Title,
1274
     misc:atom_to_binary(Node)].
1275

1276
justcreated_to_binary(J) when is_integer(J) ->
1277
    JNow = misc:usec_to_now(J),
×
1278
    {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(JNow),
×
1279
    str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
×
1280
               [Year, Month, Day, Hour, Minute, Second]);
1281
justcreated_to_binary(J) when is_atom(J) ->
1282
    misc:atom_to_binary(J).
×
1283

1284
%%--------------------
1285
%% Web Admin Host User
1286

1287
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
1288
    Acc
1289
    ++ [{<<"muc-rooms">>, <<"MUC Rooms Online">>},
×
1290
        {<<"muc-affiliations">>, <<"MUC Rooms Affiliations">>},
1291
        {<<"muc-sub">>, <<"MUC Rooms Subscriptions">>},
1292
        {<<"muc-register">>, <<"MUC Service Registration">>}].
1293

1294
web_page_hostuser(_, Host, User, #request{path = [<<"muc-rooms">> | RPath]} = R) ->
1295
    Level = 5 + length(RPath),
×
1296
    Res = ?H1GL(<<"MUC Rooms Online">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1297
          ++ [make_command(get_user_rooms,
1298
                           R,
1299
                           [{<<"user">>, User}, {<<"host">>, Host}],
1300
                           [{table_options, {2, RPath}},
1301
                            {result_links, [{room, room, Level, <<"">>}]}])],
1302
    {stop, Res};
×
1303
web_page_hostuser(_, Host, User, #request{path = [<<"muc-affiliations">>]} = R) ->
1304
    Jid = jid:encode(
×
1305
              jid:make(User, Host)),
1306
    Res = ?H1GL(<<"MUC Rooms Affiliations">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1307
          ++ [make_command(set_room_affiliation, R, [{<<"jid">>, Jid}], []),
1308
              make_command(get_room_affiliation, R, [{<<"jid">>, Jid}], [])],
1309
    {stop, Res};
×
1310
web_page_hostuser(_, Host, User, #request{path = [<<"muc-sub">> | RPath]} = R) ->
1311
    Title =
×
1312
        ?H1GLraw(<<"MUC Rooms Subscriptions">>,
1313
                 <<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
1314
                 <<"MUC/Sub">>),
1315
    Level = 5 + length(RPath),
×
1316
    Set = [make_command(subscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
×
1317
           make_command(unsubscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
1318
    Get = [make_command(get_user_subscriptions,
×
1319
                        R,
1320
                        [{<<"user">>, User}, {<<"host">>, Host}],
1321
                        [{table_options, {20, RPath}},
1322
                         {result_links, [{roomjid, room, Level, <<"">>}]}])],
1323
    {stop, Title ++ Get ++ Set};
×
1324
web_page_hostuser(_, Host, User, #request{path = [<<"muc-register">>]} = R) ->
1325
    Jid = jid:encode(
×
1326
              jid:make(User, Host)),
1327
    Res = ?H1GL(<<"MUC Service Registration">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1328
          ++ [make_command(muc_register_nick, R, [{<<"jid">>, Jid}], []),
1329
              make_command(muc_unregister_nick, R, [{<<"jid">>, Jid}], [])],
1330
    {stop, Res};
×
1331
web_page_hostuser(Acc, _, _, _) ->
1332
    Acc.
×
1333
%% @format-end
1334

1335
%%--------------------
1336
%% Web Admin Nick Register
1337

1338
%% @format-begin
1339
make_webadmin_roster_table(Service, R, RPath) ->
1340
    Nicks =
×
1341
        case make_command_raw_value(muc_get_registered_nicks, R, [{<<"service">>, Service}]) of
1342
            Ns when is_list(Ns) ->
1343
                Ns;
×
1344
            _ ->
1345
                []
×
1346
        end,
1347
    Level = 4 + length(RPath),
×
1348
    Columns = [<<"user@host">>, <<"nick">>, <<"">>],
×
1349
    Rows =
×
1350
        lists:map(fun({User, Host, Nick}) ->
1351
                     {make_command(echo,
×
1352
                                   R,
1353
                                   [{<<"sentence">>,
1354
                                     jid:encode(
1355
                                         jid:make(User, Host))}],
1356
                                   [{only, raw_and_value},
1357
                                    {result_links, [{sentence, user, Level, <<"">>}]}]),
1358
                      ?C(Nick),
1359
                      make_command(muc_unregister_nick,
1360
                                   R,
1361
                                   [{<<"user">>, User},
1362
                                    {<<"host">>, Host},
1363
                                    {<<"service">>, Service}],
1364
                                   [{only, button},
1365
                                    {style, danger},
1366
                                    {input_name_append, [User, Host, Service]}])}
1367
                  end,
1368
                  lists:keysort(1, Nicks)),
1369
    Table = make_table(20, RPath, Columns, Rows),
×
1370
    ?XE(<<"blockquote">>, [Table]).
×
1371
%% @format-end
1372

1373

1374
%%----------------------------
1375
%% Create/Delete Room
1376
%%----------------------------
1377

1378
-spec create_room(Name::binary(), Host::binary(), ServerHost::binary()) -> ok | error.
1379
%% @doc Create a room immediately with the default options.
1380
create_room(Name1, Host1, ServerHost) ->
1381
    create_room_with_opts(Name1, Host1, ServerHost, []).
×
1382

1383
create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
1384
    ServerHost = validate_host(ServerHost1, <<"serverhost">>),
×
1385
    case get_room_pid_validate(Name1, Host1, <<"service">>) of
×
1386
        {room_not_found, Name, Host} ->
1387
            %% Get the default room options from the muc configuration
1388
            DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
×
1389
            %% Change default room options as required
1390
            FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
×
1391
            RoomOpts = lists:ukeymerge(1,
×
1392
                lists:keysort(1, FormattedRoomOpts),
1393
                lists:keysort(1, DefRoomOpts)),
1394
            case mod_muc:create_room(Host, Name, RoomOpts) of
×
1395
                ok ->
1396
                    ok;
×
1397
                {error, _} ->
1398
                    throw({error, "Unable to start room"})
×
1399
            end;
1400
        {db_failure, _Name, _Host} ->
1401
            throw({error, "Database error"});
×
1402
        _ ->
1403
            throw({error, "Room already exists"})
×
1404
    end.
1405

1406
%% Create the room only in the database.
1407
%% It is required to restart the MUC service for the room to appear.
1408
muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
1409
    io:format("Creating room ~ts@~ts~n", [Name, Host]),
×
1410
    mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts).
×
1411

1412
-spec destroy_room(Name::binary(), Host::binary()) -> ok | {error, room_not_exists}.
1413
%% @doc Destroy the room immediately.
1414
%% If the room has participants, they are not notified that the room was destroyed;
1415
%% they will notice when they try to chat and receive an error that the room doesn't exist.
1416
destroy_room(Name1, Service1) ->
1417
    case get_room_pid_validate(Name1, Service1, <<"service">>) of
×
1418
        {room_not_found, _, _} ->
1419
            throw({error, "Room doesn't exists"});
×
1420
        {db_failure, _Name, _Host} ->
1421
            throw({error, "Database error"});
×
1422
        {Pid, _, _} ->
1423
            mod_muc_room:destroy(Pid),
×
1424
            ok
×
1425
    end.
1426

1427
destroy_room({N, H, SH}) ->
1428
    io:format("Destroying room: ~ts@~ts - vhost: ~ts~n", [N, H, SH]),
×
1429
    destroy_room(N, H).
×
1430

1431

1432
%%----------------------------
1433
%% Destroy Rooms in File
1434
%%----------------------------
1435

1436
%% The format of the file is: one chatroom JID per line
1437
%% The file encoding must be UTF-8
1438

1439
destroy_rooms_file(Filename) ->
1440
    {ok, F} = file:open(Filename, [read]),
×
1441
    RJID = read_room(F),
×
1442
    Rooms = read_rooms(F, RJID, []),
×
1443
    file:close(F),
×
1444
    [destroy_room(A) || A <- Rooms],
×
1445
    ok.
×
1446

1447
read_rooms(_F, eof, L) ->
1448
    L;
×
1449
read_rooms(F, no_room, L) ->
1450
    RJID2 = read_room(F),
×
1451
    read_rooms(F, RJID2, L);
×
1452
read_rooms(F, RJID, L) ->
1453
    RJID2 = read_room(F),
×
1454
    read_rooms(F, RJID2, [RJID | L]).
×
1455

1456
read_room(F) ->
1457
    case io:get_line(F, "") of
×
1458
        eof -> eof;
×
1459
        String ->
1460
            case io_lib:fread("~ts", String) of
×
1461
                {ok, [RoomJID], _} -> split_roomjid(list_to_binary(RoomJID));
×
1462
                {error, What} ->
1463
                    io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
×
1464
            end
1465
    end.
1466

1467
%% This function is quite rudimentary
1468
%% and may not be accurate
1469
split_roomjid(RoomJID) ->
1470
    split_roomjid2(binary:split(RoomJID, <<"@">>)).
×
1471
split_roomjid2([Name, Host]) ->
1472
    [_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
×
1473
    {Name, Host, ServerHost};
×
1474
split_roomjid2(_) ->
1475
    no_room.
×
1476

1477
%%----------------------------
1478
%% Create Rooms in File
1479
%%----------------------------
1480

1481
create_rooms_file(Filename) ->
1482
    {ok, F} = file:open(Filename, [read]),
×
1483
    RJID = read_room(F),
×
1484
    Rooms = read_rooms(F, RJID, []),
×
1485
    file:close(F),
×
1486
    HostsDetails = get_hosts_details(Rooms),
×
1487
    [muc_create_room(HostsDetails, A) || A <- Rooms],
×
1488
    ok.
×
1489

1490
muc_create_room(HostsDetails, {_, Host, _} = RoomTuple) ->
1491
    {_Host, ServerHost, DefRoomOpts} = get_host_details(Host, HostsDetails),
×
1492
    muc_create_room(ServerHost, RoomTuple, DefRoomOpts).
×
1493

1494
get_hosts_details(Rooms) ->
1495
    Hosts = lists:uniq([Host || {_, Host, _} <- Rooms]),
×
1496
    lists:map(fun(H) ->
×
1497
                      SH = get_room_serverhost(H),
×
1498
                      {H, SH, mod_muc_opt:default_room_options(SH)}
×
1499
              end, Hosts).
1500

1501
get_host_details(Host, ServerHostsDetails) ->
1502
    lists:keyfind(Host, 1, ServerHostsDetails).
×
1503

1504
%%---------------------------------
1505
%% List/Delete Unused/Empty Rooms
1506
%%---------------------------------
1507

1508
%%---------------
1509
%% Control
1510

1511
rooms_unused_list(Service, Days) ->
1512
    rooms_report(unused, list, Service, Days).
×
1513
rooms_unused_destroy(Service, Days) ->
1514
    rooms_report(unused, destroy, Service, Days).
×
1515

1516
rooms_empty_list(Service) ->
1517
    rooms_report(empty, list, Service, 0).
×
1518
rooms_empty_destroy(Service) ->
1519
    rooms_report(empty, destroy, Service, 0).
×
1520

1521
rooms_empty_destroy_restuple(Service) ->
1522
    DestroyedRooms = rooms_report(empty, destroy, Service, 0),
×
1523
    NumberBin = integer_to_binary(length(DestroyedRooms)),
×
1524
    {ok, <<"Destroyed rooms: ", NumberBin/binary>>}.
×
1525

1526
rooms_report(Method, Action, Service, Days) ->
1527
    {NA, NP, RP} = muc_unused(Method, Action, Service, Days),
×
1528
    io:format("rooms ~ts: ~p out of ~p~n", [Method, NP, NA]),
×
1529
    [<<R/binary, "@", H/binary>> || {R, H, _SH, _P} <- RP].
×
1530

1531
muc_unused(Method, Action, Service, Last_allowed) ->
1532
    %% Get all required info about all existing rooms
1533
    Rooms_all = get_all_rooms(Service, erlang:system_time(microsecond) - Last_allowed*24*60*60*1000),
×
1534

1535
    %% Decide which ones pass the requirements
1536
    Rooms_pass = decide_rooms(Method, Rooms_all, Last_allowed),
×
1537

1538
    Num_rooms_all = length(Rooms_all),
×
1539
    Num_rooms_pass = length(Rooms_pass),
×
1540

1541
    %% Perform the desired action for matching rooms
1542
    act_on_rooms(Method, Action, Rooms_pass),
×
1543

1544
    {Num_rooms_all, Num_rooms_pass, Rooms_pass}.
×
1545

1546
%%---------------
1547
%% Get info
1548

1549
get_online_rooms(ServiceArg) ->
1550
    Hosts = find_services(ServiceArg),
×
1551
    lists:flatmap(
×
1552
      fun(Host) ->
1553
          ServerHost = get_room_serverhost(Host),
×
1554
          [{RoomName, RoomHost, ServerHost, Pid}
×
1555
           || {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
×
1556
      end, Hosts).
1557

1558
get_all_rooms(ServiceArg, Timestamp) ->
1559
    Hosts = find_services(ServiceArg),
×
1560
    lists:flatmap(
×
1561
      fun(Host) ->
1562
              get_all_rooms2(Host, Timestamp)
×
1563
      end, Hosts).
1564

1565
get_all_rooms2(Host, Timestamp) ->
1566
    ServerHost = ejabberd_router:host_of_route(Host),
×
1567
    OnlineRooms = get_online_rooms(Host),
×
1568
    OnlineMap = lists:foldl(
×
1569
        fun({Room, _, _, _}, Map) ->
1570
            Map#{Room => 1}
×
1571
        end, #{}, OnlineRooms),
1572

1573
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
×
1574
    DbRooms =
×
1575
    case {erlang:function_exported(Mod, get_rooms_without_subscribers, 2),
1576
          erlang:function_exported(Mod, get_hibernated_rooms_older_than, 3)} of
1577
        {_, true} ->
1578
            Mod:get_hibernated_rooms_older_than(ServerHost, Host, Timestamp);
×
1579
        {true, _} ->
1580
            Mod:get_rooms_without_subscribers(ServerHost, Host);
×
1581
        _ ->
1582
            Mod:get_rooms(ServerHost, Host)
×
1583
    end,
1584
    StoredRooms = lists:filtermap(
×
1585
        fun(#muc_room{name_host = {Room, _}, opts = Opts}) ->
1586
            case maps:is_key(Room, OnlineMap) of
×
1587
                true ->
1588
                    false;
×
1589
                _ ->
1590
                    {true, {Room, Host, ServerHost, Opts}}
×
1591
            end
1592
        end, DbRooms),
1593
    OnlineRooms ++ StoredRooms.
×
1594

1595
get_room_config(Room_pid) ->
1596
    {ok, R} = mod_muc_room:get_config(Room_pid),
×
1597
    R.
×
1598

1599
get_room_state(Room_pid) ->
1600
    {ok, R} = mod_muc_room:get_state(Room_pid),
×
1601
    R.
×
1602

1603
%%---------------
1604
%% Decide
1605

1606
decide_rooms(Method, Rooms, Last_allowed) ->
1607
    Decide = fun(R) -> decide_room(Method, R, Last_allowed) end,
×
1608
    lists:filter(Decide, Rooms).
×
1609

1610
decide_room(unused, {_Room_name, _Host, ServerHost, Room_pid}, Last_allowed) ->
1611
    NodeStartTime = erlang:system_time(microsecond) -
×
1612
                    1000000*(erlang:monotonic_time(second)-ejabberd_config:get_node_start()),
1613
    OnlyHibernated = case mod_muc_opt:hibernation_timeout(ServerHost) of
×
1614
        Value when Value < Last_allowed*24*60*60*1000 ->
1615
            true;
×
1616
        _ ->
1617
            false
×
1618
        end,
1619
    {Just_created, Num_users} =
×
1620
    case Room_pid of
1621
        Pid when is_pid(Pid) andalso OnlyHibernated ->
1622
            {erlang:system_time(microsecond), 0};
×
1623
        Pid when is_pid(Pid) ->
1624
            case mod_muc_room:get_state(Room_pid) of
×
1625
                {ok, #state{just_created = JC, users = U}} ->
1626
                    {JC, maps:size(U)};
×
1627
                _ ->
1628
                    {erlang:system_time(microsecond), 0}
×
1629
            end;
1630
        Opts ->
1631
            case lists:keyfind(hibernation_time, 1, Opts) of
×
1632
                false ->
1633
                    {NodeStartTime, 0};
×
1634
                {_, undefined} ->
1635
                    {NodeStartTime, 0};
×
1636
                {_, T} ->
1637
                    {T, 0}
×
1638
            end
1639
    end,
1640
    Last = case Just_created of
×
1641
               true ->
1642
                   0;
×
1643
               _ ->
1644
                   (erlang:system_time(microsecond)
1645
                    - Just_created) div 1000000
×
1646
           end,
1647
    case {Num_users, seconds_to_days(Last)} of
×
1648
        {0, Last_days} when (Last_days >= Last_allowed) ->
1649
            true;
×
1650
        _ ->
1651
            false
×
1652
    end;
1653
decide_room(empty, {Room_name, Host, ServerHost, Room_pid}, _Last_allowed) ->
1654
    case gen_mod:is_loaded(ServerHost, mod_mam) of
×
1655
        true ->
1656
            Room_options = case Room_pid of
×
1657
                               _ when is_pid(Room_pid) ->
1658
                                   get_room_options(Room_pid);
×
1659
                               Opts ->
1660
                                   Opts
×
1661
                           end,
1662
            case lists:keyfind(<<"mam">>, 1, Room_options) of
×
1663
                {<<"mam">>, <<"true">>} ->
1664
                    mod_mam:is_empty_for_room(ServerHost, Room_name, Host);
×
1665
                _ ->
1666
                    false
×
1667
            end;
1668
        _ ->
1669
            false
×
1670
    end.
1671

1672
seconds_to_days(S) ->
1673
    S div (60*60*24).
×
1674

1675
%%---------------
1676
%% Act
1677

1678
act_on_rooms(Method, Action, Rooms) ->
1679
    Delete = fun(Room) ->
×
1680
                     act_on_room(Method, Action, Room)
×
1681
             end,
1682
    lists:foreach(Delete, Rooms).
×
1683

1684
act_on_room(Method, destroy, {N, H, _SH, Pid}) ->
1685
    Message = iolist_to_binary(io_lib:format(
×
1686
        <<"Room destroyed by rooms_~s_destroy.">>, [Method])),
1687
    case Pid of
×
1688
        V when is_pid(V) ->
1689
            mod_muc_room:destroy(Pid, Message);
×
1690
        _ ->
1691
            case get_room_pid(N, H) of
×
1692
                Pid2 when is_pid(Pid2) ->
1693
                    mod_muc_room:destroy(Pid2, Message);
×
1694
                _ ->
1695
                    ok
×
1696
            end
1697
    end;
1698
act_on_room(_Method, list, _) ->
1699
    ok.
×
1700

1701

1702
%%----------------------------
1703
%% Change Room Option
1704
%%----------------------------
1705

1706
get_room_occupants(Room, Host) ->
1707
    case get_room_pid_validate(Room, Host, <<"service">>) of
×
1708
        {Pid, _, _} when is_pid(Pid) -> get_room_occupants(Pid);
×
1709
        _ -> throw({error, room_not_found})
×
1710
    end.
1711

1712
get_room_occupants(Pid) ->
1713
    S = get_room_state(Pid),
×
1714
    lists:map(
×
1715
      fun({_LJID, Info}) ->
1716
              {jid:encode(Info#user.jid),
×
1717
               Info#user.nick,
1718
               atom_to_list(Info#user.role)}
1719
      end,
1720
      maps:to_list(S#state.users)).
1721

1722
get_room_occupants_number(Room, Host) ->
1723
    case get_room_pid_validate(Room, Host, <<"service">>) of
×
1724
        {Pid, _, _} when is_pid(Pid)->
1725
            {ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid),
×
1726
            N;
×
1727
        _ ->
1728
            throw({error, room_not_found})
×
1729
    end.
1730

1731
%%----------------------------
1732
%% Send Direct Invitation
1733
%%----------------------------
1734
%% http://xmpp.org/extensions/xep-0249.html
1735

1736
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) when is_binary(UsersString) ->
1737
    UsersStrings = binary:split(UsersString, <<":">>, [global]),
×
1738
    send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings);
×
1739
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings) ->
1740
    case jid:make(RoomName, RoomService) of
×
1741
        error ->
1742
            throw({error, "Invalid 'roomname' or 'service'"});
×
1743
        RoomJid ->
1744
            XmlEl = build_invitation(Password, Reason, RoomJid),
×
1745
            Users = get_users_to_invite(RoomJid, UsersStrings),
×
1746
            [send_direct_invitation(RoomJid, UserJid, XmlEl)
×
1747
             || UserJid <- Users],
×
1748
            ok
×
1749
    end.
1750

1751
get_users_to_invite(RoomJid, UsersStrings) ->
1752
    OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
×
1753
                                         RoomJid#jid.lserver),
1754
    OccupantsJids = try [jid:decode(JidString)
×
1755
                         || {JidString, _Nick, _} <- OccupantsTuples]
×
1756
                    catch _:{bad_jid, _} -> throw({error, "Malformed JID of invited user"})
×
1757
                    end,
1758
    lists:filtermap(
×
1759
      fun(UserString) ->
1760
              UserJid = jid:decode(UserString),
×
1761
              Val = lists:all(fun(OccupantJid) ->
×
1762
                                      UserJid#jid.luser /= OccupantJid#jid.luser
×
1763
                                          orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
×
1764
                              end,
1765
                              OccupantsJids),
1766
              case {UserJid#jid.luser, Val} of
×
1767
                  {<<>>, _} -> false;
×
1768
                  {_, true} -> {true, UserJid};
×
1769
                  _ -> false
×
1770
              end
1771
      end,
1772
      UsersStrings).
1773

1774
build_invitation(Password, Reason, RoomJid) ->
1775
    Invite = #x_conference{jid = RoomJid,
×
1776
                           password = case Password of
1777
                                          <<"none">> -> <<>>;
×
1778
                                          _ -> Password
×
1779
                                      end,
1780
                           reason = case Reason of
1781
                                        <<"none">> -> <<>>;
×
1782
                                        _ -> Reason
×
1783
                                    end},
1784
    #message{sub_els = [Invite]}.
×
1785

1786
send_direct_invitation(FromJid, UserJid, Msg) ->
1787
    ejabberd_router:route(xmpp:set_from_to(Msg, FromJid, UserJid)).
×
1788

1789
%%----------------------------
1790
%% Change Room Option
1791
%%----------------------------
1792

1793
-spec change_room_option(Name::binary(), Service::binary(), Option::binary(),
1794
                         Value::atom() | integer() | string()) -> ok | mod_muc_log_not_enabled.
1795
%% @doc Change an option in an existing room.
1796
%% Requires the name of the room, the MUC service where it exists,
1797
%% the option to change (for example title or max_users),
1798
%% and the value to assign to the new option.
1799
%% For example:
1800
%% `change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)'
1801
change_room_option(Name, Service, OptionString, ValueString) ->
1802
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
1803
        {room_not_found, _, _} ->
1804
            throw({error, "Room not found"});
×
1805
        {db_failure, _Name, _Host} ->
1806
            throw({error, "Database error"});
×
1807
        {Pid, _, _} ->
1808
            {Option, Value} = format_room_option(OptionString, ValueString),
×
1809
            change_room_option(Pid, Option, Value)
×
1810
    end.
1811

1812
change_room_option(Pid, Option, Value) ->
1813
    case {Option,
×
1814
          gen_mod:is_loaded((get_room_state(Pid))#state.server_host, mod_muc_log)} of
1815
        {logging, false} ->
1816
            mod_muc_log_not_enabled;
×
1817
        _ ->
1818
            Config = get_room_config(Pid),
×
1819
            Config2 = change_option(Option, Value, Config),
×
1820
            {ok, _} = mod_muc_room:set_config(Pid, Config2),
×
1821
            ok
×
1822
    end.
1823

1824
format_room_option(OptionString, ValueString) ->
1825
    Option = misc:binary_to_atom(OptionString),
×
1826
    Value = case Option of
×
1827
                title -> ValueString;
×
1828
                description -> ValueString;
×
1829
                password -> ValueString;
×
1830
                subject ->ValueString;
×
1831
                subject_author ->ValueString;
×
1832
                max_users -> try_convert_integer(Option, ValueString);
×
1833
                voice_request_min_interval -> try_convert_integer(Option, ValueString);
×
1834
                vcard -> ValueString;
×
1835
                vcard_xupdate when ValueString /= <<"undefined">>,
1836
                                   ValueString /= <<"external">> ->
1837
                    ValueString;
×
1838
                lang -> ValueString;
×
1839
                pubsub -> ValueString;
×
1840
                affiliations ->
1841
                    [parse_affiliation_string(Opt) || Opt <- str:tokens(ValueString, <<";,">>)];
×
1842
                subscribers ->
1843
                    [parse_subscription_string(Opt) || Opt <- str:tokens(ValueString, <<";,">>)];
×
1844
                allow_private_messages_from_visitors when
1845
                      (ValueString == <<"anyone">>) or
1846
                      (ValueString == <<"moderators">>) or
1847
                      (ValueString == <<"nobody">>) -> binary_to_existing_atom(ValueString, utf8);
×
1848
                allowpm when
1849
                      (ValueString == <<"anyone">>) or
1850
                      (ValueString == <<"participants">>) or
1851
                      (ValueString == <<"moderators">>) or
1852
                      (ValueString == <<"none">>) -> binary_to_existing_atom(ValueString, utf8);
×
1853
                presence_broadcast when
1854
                      (ValueString == <<"participant">>) or
1855
                      (ValueString == <<"moderator">>) or
1856
                      (ValueString == <<"visitor">>) -> binary_to_existing_atom(ValueString, utf8);
×
1857
                _ when ValueString == <<"true">> -> true;
×
1858
                _ when ValueString == <<"false">> -> false;
×
1859
                _ -> throw_error(Option, ValueString)
×
1860
            end,
1861
    {Option, Value}.
×
1862

1863
try_convert_integer(Option, ValueString) ->
1864
    try binary_to_integer(ValueString) of
×
1865
        I when is_integer(I) -> I
×
1866
    catch _:badarg ->
1867
        throw_error(Option, ValueString)
×
1868
    end.
1869

1870
throw_error(O, V) ->
1871
    throw({error, "Invalid value for that option", O, V}).
×
1872

1873
parse_affiliation_string(String) ->
1874
    {Type, JidS} = case String of
×
1875
                       %% Old syntax
1876
                       <<"owner:", Jid/binary>> -> {owner, Jid};
×
1877
                       <<"admin:", Jid/binary>> -> {admin, Jid};
×
1878
                       <<"member:", Jid/binary>> -> {member, Jid};
×
1879
                       <<"outcast:", Jid/binary>> -> {outcast, Jid};
×
1880
                       %% New syntax
1881
                       <<"owner=", Jid/binary>> -> {owner, Jid};
×
1882
                       <<"admin=", Jid/binary>> -> {admin, Jid};
×
1883
                       <<"member=", Jid/binary>> -> {member, Jid};
×
1884
                       <<"outcast=", Jid/binary>> -> {outcast, Jid};
×
1885
                       _ -> throw({error, "Invalid 'affiliation'"})
×
1886
                   end,
1887
    try jid:decode(JidS) of
×
1888
        #jid{luser = U, lserver = S, lresource = R} ->
1889
            {{U, S, R}, {Type, <<>>}}
×
1890
    catch _:{bad_jid, _} ->
1891
        throw({error, "Malformed JID in affiliation"})
×
1892
    end.
1893

1894
parse_subscription_string(String) ->
1895
    case str:tokens(String, <<"=:">>) of
×
1896
        [_] ->
1897
            throw({error, "Invalid 'subscribers' - missing nick"});
×
1898
        [_, _] ->
1899
            throw({error, "Invalid 'subscribers' - missing nodes"});
×
1900
        [JidS, Nick | Nodes] ->
1901
            Nodes2 = parse_nodes(Nodes, []),
×
1902
            try jid:decode(JidS) of
×
1903
                Jid ->
1904
                    {Jid, Nick, Nodes2}
×
1905
            catch _:{bad_jid, _} ->
1906
                throw({error, "Malformed JID in 'subscribers'"})
×
1907
            end
1908
    end.
1909

1910
parse_nodes([], Acc) ->
1911
    Acc;
×
1912
parse_nodes([<<"presence">> | Rest], Acc) ->
1913
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PRESENCE | Acc]);
×
1914
parse_nodes([<<"messages">> | Rest], Acc) ->
1915
    parse_nodes(Rest, [?NS_MUCSUB_NODES_MESSAGES | Acc]);
×
1916
parse_nodes([<<"participants">> | Rest], Acc) ->
1917
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PARTICIPANTS | Acc]);
×
1918
parse_nodes([<<"affiliations">> | Rest], Acc) ->
1919
    parse_nodes(Rest, [?NS_MUCSUB_NODES_AFFILIATIONS | Acc]);
×
1920
parse_nodes([<<"subject">> | Rest], Acc) ->
1921
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBJECT | Acc]);
×
1922
parse_nodes([<<"config">> | Rest], Acc) ->
1923
    parse_nodes(Rest, [?NS_MUCSUB_NODES_CONFIG | Acc]);
×
1924
parse_nodes([<<"system">> | Rest], Acc) ->
1925
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SYSTEM | Acc]);
×
1926
parse_nodes([<<"subscribers">> | Rest], Acc) ->
1927
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBSCRIBERS | Acc]);
×
1928
parse_nodes(_, _) ->
1929
    throw({error, "Invalid 'subscribers' - unknown node name used"}).
×
1930

1931
-spec get_room_pid_validate(binary(), binary(), binary()) ->
1932
    {pid() | room_not_found | db_failure, binary(), binary()}.
1933
get_room_pid_validate(Name, Service, ServiceArg) ->
UNCOV
1934
    Name2 = validate_room(Name),
9✔
UNCOV
1935
    {ServerHost, Service2} = validate_muc2(Service, ServiceArg),
9✔
UNCOV
1936
    case mod_muc:unhibernate_room(ServerHost, Service2, Name2) of
9✔
1937
        {error, notfound} ->
1938
            {room_not_found, Name2, Service2};
×
1939
        {error, db_failure} ->
1940
            {db_failure, Name2, Service2};
×
1941
        {ok, Pid} ->
UNCOV
1942
            {Pid, Name2, Service2}
9✔
1943
    end.
1944

1945
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
1946
-spec get_room_pid(binary(), binary()) -> pid() | room_not_found | db_failure | invalid_service | unknown_service.
1947
get_room_pid(Name, Service) ->
1948
    try get_room_serverhost(Service) of
×
1949
        ServerHost ->
1950
            case mod_muc:unhibernate_room(ServerHost, Service, Name) of
×
1951
                {error, notfound} ->
1952
                    room_not_found;
×
1953
                {error, db_failure} ->
1954
                    db_failure;
×
1955
                {ok, Pid} ->
1956
                    Pid
×
1957
            end
1958
    catch
1959
        error:{invalid_domain, _} ->
1960
            invalid_service;
×
1961
        error:{unregistered_route, _} ->
1962
            unknown_service
×
1963
    end.
1964

1965
room_diagnostics(Name, Service) ->
1966
    try get_room_serverhost(Service) of
×
1967
        ServerHost ->
1968
            RMod = gen_mod:ram_db_mod(ServerHost, mod_muc),
×
1969
            case RMod:find_online_room(ServerHost, Name, Service) of
×
1970
                error ->
1971
                    room_hibernated;
×
1972
                {ok, Pid} ->
1973
                    case rpc:pinfo(Pid, [current_stacktrace, message_queue_len, messages]) of
×
1974
                        [{_, R}, {_, QL}, {_, Q}] ->
1975
                            #{stacktrace => R, queue_size => QL, queue => lists:sublist(Q, 10)};
×
1976
                        _ ->
1977
                            unable_to_probe_process
×
1978
                    end
1979
            end
1980
    catch
1981
        error:{invalid_domain, _} ->
1982
            invalid_service;
×
1983
        error:{unregistered_route, _} ->
1984
            unknown_service
×
1985
    end.
1986

1987
%% It is required to put explicitly all the options because
1988
%% the record elements are replaced at compile time.
1989
%% So, this can't be parametrized.
1990
change_option(Option, Value, Config) ->
1991
    case Option of
×
1992
        allow_change_subj -> Config#config{allow_change_subj = Value};
×
1993
        allowpm -> Config#config{allowpm = Value};
×
1994
        allow_private_messages_from_visitors -> Config#config{allow_private_messages_from_visitors = Value};
×
1995
        allow_query_users -> Config#config{allow_query_users = Value};
×
1996
        allow_subscription -> Config#config{allow_subscription = Value};
×
1997
        allow_user_invites -> Config#config{allow_user_invites = Value};
×
1998
        allow_visitor_nickchange -> Config#config{allow_visitor_nickchange = Value};
×
1999
        allow_visitor_status -> Config#config{allow_visitor_status = Value};
×
2000
        allow_voice_requests -> Config#config{allow_voice_requests = Value};
×
2001
        anonymous -> Config#config{anonymous = Value};
×
2002
        captcha_protected -> Config#config{captcha_protected = Value};
×
2003
        description -> Config#config{description = Value};
×
2004
        enable_hats -> Config#config{enable_hats = Value};
×
2005
        lang -> Config#config{lang = Value};
×
2006
        logging -> Config#config{logging = Value};
×
2007
        mam -> Config#config{mam = Value};
×
2008
        max_users -> Config#config{max_users = Value};
×
2009
        members_by_default -> Config#config{members_by_default = Value};
×
2010
        members_only -> Config#config{members_only = Value};
×
2011
        moderated -> Config#config{moderated = Value};
×
2012
        password -> Config#config{password = Value};
×
2013
        password_protected -> Config#config{password_protected = Value};
×
2014
        persistent -> Config#config{persistent = Value};
×
2015
        presence_broadcast -> Config#config{presence_broadcast = Value};
×
2016
        public -> Config#config{public = Value};
×
2017
        public_list -> Config#config{public_list = Value};
×
2018
        pubsub -> Config#config{pubsub = Value};
×
2019
        title -> Config#config{title = Value};
×
2020
        vcard -> Config#config{vcard = Value};
×
2021
        vcard_xupdate -> Config#config{vcard_xupdate = Value};
×
2022
        voice_request_min_interval -> Config#config{voice_request_min_interval = Value}
×
2023
    end.
2024

2025
%%----------------------------
2026
%% Get Room Options
2027
%%----------------------------
2028

2029
get_room_options(Name, Service) ->
2030
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2031
        {Pid, _, _} when is_pid(Pid) -> get_room_options(Pid);
×
2032
        _ -> []
×
2033
    end.
2034

2035
get_room_options(Pid) ->
2036
    Config = get_room_config(Pid),
×
2037
    get_options(Config).
×
2038

2039
get_options(Config) ->
2040
    Fields = [misc:atom_to_binary(Field) || Field <- record_info(fields, config)],
×
2041
    [config | ValuesRaw] = tuple_to_list(Config),
×
2042
    Values = lists:map(fun(V) when is_atom(V) -> misc:atom_to_binary(V);
×
2043
                          (V) when is_integer(V) -> integer_to_binary(V);
×
2044
                          (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
×
2045
                          (V) -> V end, ValuesRaw),
×
2046
    lists:zip(Fields, Values).
×
2047

2048
%%----------------------------
2049
%% Get Room Affiliations
2050
%%----------------------------
2051

2052
%% @spec(Name::binary(), Service::binary()) ->
2053
%%    [{Username::string(), Domain::string(), Role::string(), Reason::string()}]
2054
%% @doc Get the affiliations of  the room Name@Service.
2055
get_room_affiliations(Name, Service) ->
2056
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2057
        {Pid, _, _} when is_pid(Pid) ->
2058
            %% Get the PID of the online room, then request its state
2059
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
2060
            Affiliations = maps:to_list(StateData#state.affiliations),
×
2061
            lists:map(
×
2062
              fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
2063
                      {Uname, Domain, Aff, Reason};
×
2064
                 ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
2065
                      {Uname, Domain, Aff, <<>>}
×
2066
              end, Affiliations);
2067
        {db_failure, _Name, _Host} ->
2068
            throw({error, "Database error"});
×
2069
        _ ->
2070
            throw({error, "The room does not exist."})
×
2071
    end.
2072

2073
%% @spec(Name::binary(), Service::binary()) ->
2074
%%    [{JID::string(), Role::string(), Reason::string()}]
2075
%% @doc Get the affiliations of  the room Name@Service.
2076
get_room_affiliations_v3(Name, Service) ->
2077
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2078
        {Pid, _, _} when is_pid(Pid) ->
2079
            %% Get the PID of the online room, then request its state
2080
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
2081
            Affiliations = maps:to_list(StateData#state.affiliations),
×
2082
            lists:map(
×
2083
              fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
2084
                      Jid = makeencode(Uname, Domain),
×
2085
                      {Jid, Aff, Reason};
×
2086
                 ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
2087
                      Jid = makeencode(Uname, Domain),
×
2088
                      {Jid, Aff, <<>>}
×
2089
              end, Affiliations);
2090
        {db_failure, _Name, _Host} ->
2091
            throw({error, "Database error"});
×
2092
        _ ->
2093
            throw({error, "The room does not exist."})
×
2094
    end.
2095

2096
get_room_history(Name, Service) ->
2097
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2098
        {Pid, _, _} when is_pid(Pid) ->
2099
            case mod_muc_room:get_state(Pid) of
×
2100
                {ok, StateData} ->
2101
                    History = p1_queue:to_list((StateData#state.history)#lqueue.queue),
×
2102
                    lists:map(
×
2103
                        fun({_Nick, Packet, _HaveSubject, TimeStamp, _Size}) ->
2104
                            {xmpp_util:encode_timestamp(TimeStamp),
×
2105
                             ejabberd_web_admin:pretty_print_xml(xmpp:encode(Packet))}
2106
                        end, History);
2107
                _ ->
2108
                    throw({error, "Unable to fetch room state."})
×
2109
            end;
2110
        {db_failure, _Name, _Host} ->
2111
            throw({error, "Database error"});
×
2112
        _ ->
2113
            throw({error, "The room does not exist."})
×
2114
    end.
2115

2116
%%----------------------------
2117
%% Get Room Affiliation
2118
%%----------------------------
2119

2120
%% @spec(Name::binary(), Service::binary(), JID::binary()) ->
2121
%%    {Affiliation::string()}
2122
%% @doc Get affiliation of a user in the room Name@Service.
2123

2124
get_room_affiliation(Name, Service, JID) ->
2125
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2126
        {Pid, _, _} when is_pid(Pid) ->
2127
            %% Get the PID of the online room, then request its state
2128
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
2129
            UserJID = jid:decode(JID),
×
2130
            mod_muc_room:get_affiliation(UserJID, StateData);
×
2131
        {db_failure, _Name, _Host} ->
2132
            throw({error, "Database error"});
×
2133
        _ ->
2134
            throw({error, "The room does not exist."})
×
2135
    end.
2136

2137
%%----------------------------
2138
%% Change Room Affiliation
2139
%%----------------------------
2140

2141
set_room_affiliation(Name, Service, User, Host, AffiliationString) ->
UNCOV
2142
    set_room_affiliation(Name, Service, makeencode(User, Host), AffiliationString).
9✔
2143

2144
%% @spec(Name, Service, JID, AffiliationString) -> ok | {error, Error}
2145
%%       Name = binary()
2146
%%       Service = binary()
2147
%%       JID = binary()
2148
%%       AffiliationString = "outcast" | "none" | "member" | "admin" | "owner"
2149
%% @doc Set the affiliation of JID in the room Name@Service.
2150
%% If the affiliation is 'none', the action is to remove,
2151
%% In any other case the action will be to create the affiliation.
2152
set_room_affiliation(Name, Service, JID, AffiliationString) ->
UNCOV
2153
    Affiliation = case AffiliationString of
9✔
2154
                      <<"outcast">> -> outcast;
×
2155
                      <<"none">> -> none;
×
UNCOV
2156
                      <<"member">> -> member;
9✔
2157
                      <<"admin">> -> admin;
×
2158
                      <<"owner">> -> owner;
×
2159
                      _ ->
2160
                          throw({error, "Invalid affiliation"})
×
2161
                  end,
UNCOV
2162
    case get_room_pid_validate(Name, Service, <<"service">>) of
9✔
2163
        {Pid, _, _} when is_pid(Pid) ->
2164
            %% Get the PID for the online room so we can get the state of the room
UNCOV
2165
            case mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>) of
9✔
2166
                {ok, _} ->
UNCOV
2167
                    ok;
9✔
2168
                {error, notfound} ->
2169
                    throw({error, "Room doesn't exists"});
×
2170
                {error, _} ->
2171
                    throw({error, "Unable to perform change"})
×
2172
            end;
2173
        {db_failure, _Name, _Host} ->
2174
            throw({error, "Database error"});
×
2175
        _ ->
2176
            throw({error, "Room doesn't exists"})
×
2177
    end.
2178

2179
%%%
2180
%%% MUC Subscription
2181
%%%
2182

2183
subscribe_room(Username, Host, Nick, Name, Service, Nodes) ->
2184
    subscribe_room(makeencode(Username, Host), Nick,
×
2185
                   makeencode(Name, Service), Nodes).
2186

2187
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
2188
    throw({error, "Nickname must be set"});
×
2189
subscribe_room(User, Nick, Room, Nodes) when is_binary(Nodes) ->
2190
    NodeList = re:split(Nodes, "\\h*,\\h*"),
×
2191
    subscribe_room(User, Nick, Room, NodeList);
×
2192
subscribe_room(User, Nick, Room, NodeList) ->
2193
    try jid:decode(Room) of
×
2194
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
2195
            try jid:decode(User) of
×
2196
                UserJID1 ->
2197
                    UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
×
2198
                    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2199
                        {Pid, _, _} when is_pid(Pid) ->
2200
                            case mod_muc_room:subscribe(
×
2201
                                   Pid, UserJID, Nick, NodeList) of
2202
                                {ok, SubscribedNodes} ->
2203
                                    SubscribedNodes;
×
2204
                                {error, Reason} ->
2205
                                    throw({error, binary_to_list(Reason)})
×
2206
                            end;
2207
                        {db_failure, _Name, _Host} ->
2208
                            throw({error, "Database error"});
×
2209
                        _ ->
2210
                            throw({error, "The room does not exist"})
×
2211
                    end
2212
            catch _:{bad_jid, _} ->
2213
                    throw({error, "Malformed user JID"})
×
2214
            end;
2215
        _ ->
2216
            throw({error, "Malformed room JID"})
×
2217
    catch _:{bad_jid, _} ->
2218
            throw({error, "Malformed room JID"})
×
2219
    end.
2220

2221
subscribe_room_many_v3(List, Name, Service, Nodes) ->
2222
    List2 = [{makeencode(User, Host), Nick} || {User, Host, Nick} <- List],
×
2223
    subscribe_room_many(List2, makeencode(Name, Service), Nodes).
×
2224

2225
subscribe_room_many(Users, Room, Nodes) ->
2226
    MaxUsers = mod_muc_admin_opt:subscribe_room_many_max_users(global),
×
2227
    if
×
2228
        length(Users) > MaxUsers ->
2229
            throw({error, "Too many users in subscribe_room_many command"});
×
2230
        true ->
2231
            lists:foreach(
×
2232
              fun({User, Nick}) ->
2233
                      subscribe_room(User, Nick, Room, Nodes)
×
2234
              end, Users)
2235
    end.
2236

2237
unsubscribe_room(User, Host, Name, Service) ->
2238
    unsubscribe_room(makeencode(User, Host),
×
2239
                     makeencode(Name, Service)).
2240

2241
unsubscribe_room(User, Room) ->
2242
    try jid:decode(Room) of
×
2243
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
2244
            try jid:decode(User) of
×
2245
                UserJID ->
2246
                    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2247
                        {Pid, _, _} when is_pid(Pid) ->
2248
                            case mod_muc_room:unsubscribe(Pid, UserJID) of
×
2249
                                ok ->
2250
                                    ok;
×
2251
                                {error, Reason} ->
2252
                                    throw({error, binary_to_list(Reason)})
×
2253
                            end;
2254
                        {db_failure, _Name, _Host} ->
2255
                            throw({error, "Database error"});
×
2256
                        _ ->
2257
                            throw({error, "The room does not exist"})
×
2258
                    end
2259
            catch _:{bad_jid, _} ->
2260
                    throw({error, "Malformed user JID"})
×
2261
            end;
2262
        _ ->
2263
            throw({error, "Malformed room JID"})
×
2264
    catch _:{bad_jid, _} ->
2265
            throw({error, "Malformed room JID"})
×
2266
    end.
2267

2268
get_subscribers(Name, Host) ->
2269
    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2270
        {Pid, _, _} when is_pid(Pid) ->
2271
            {ok, JIDList} = mod_muc_room:get_subscribers(Pid),
×
2272
            [jid:encode(jid:remove_resource(J)) || J <- JIDList];
×
2273
        {db_failure, _Name, _Host} ->
2274
            throw({error, "Database error"});
×
2275
        _ ->
2276
            throw({error, "The room does not exist"})
×
2277
    end.
2278

2279
%%----------------------------
2280
%% Utils
2281
%%----------------------------
2282

2283
makeencode(User, Host) ->
UNCOV
2284
    jid:encode(jid:make(User, Host)).
9✔
2285

2286
-spec validate_host(Name :: binary(), ArgName::binary()) -> binary().
2287
validate_host(Name, ArgName) ->
2288
    case jid:nameprep(Name) of
×
2289
        error ->
2290
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2291
        Name2 ->
2292
            case lists:member(Name2, ejabberd_option:hosts()) of
×
2293
                false ->
2294
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2295
                _ ->
2296
                    Name2
×
2297
            end
2298
    end.
2299

2300
-spec validate_user(Name :: binary(), ArgName::binary()) -> binary().
2301
validate_user(Name, ArgName) ->
2302
    case jid:nodeprep(Name) of
×
2303
        error ->
2304
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2305
        Name2 ->
2306
            Name2
×
2307
    end.
2308

2309
-spec validate_muc(Name :: binary(), ArgName::binary()) -> binary().
2310
validate_muc(Name, ArgName) ->
2311
    case jid:nameprep(Name) of
×
2312
        error ->
2313
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2314
        Name2 ->
2315
            try get_room_serverhost(Name2) of
×
2316
                _ -> Name2
×
2317
            catch
2318
                error:{invalid_domain, _} ->
2319
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2320
                error:{unregistered_route, _} ->
2321
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
×
2322
            end
2323
    end.
2324

2325
-spec validate_muc2(Name :: binary(), ArgName::binary()) -> {binary(), binary()}.
2326
validate_muc2(Name, ArgName) ->
UNCOV
2327
    case jid:nameprep(Name) of
9✔
2328
        error ->
2329
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2330
        Name2 ->
UNCOV
2331
            try get_room_serverhost(Name2) of
9✔
UNCOV
2332
                Host -> {Host, Name2}
9✔
2333
            catch
2334
                error:{invalid_domain, _} ->
2335
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2336
                error:{unregistered_route, _} ->
2337
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
×
2338
            end
2339
    end.
2340

2341
-spec validate_room(Name :: binary()) -> binary().
2342
validate_room(Name) ->
UNCOV
2343
    case jid:nodeprep(Name) of
9✔
2344
        error ->
2345
            throw({error, <<"Invalid value of room name">>});
×
2346
        Name2 ->
UNCOV
2347
            Name2
9✔
2348
    end.
2349

2350
find_service(global) ->
2351
    global;
×
2352
find_service(ServerHost) ->
2353
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
×
2354

2355
find_services_validate(Global, _Name) when Global == global;
2356
    Global == <<"global">> ->
2357
    find_services(Global);
×
2358
find_services_validate(Service, Name) ->
2359
    Service2 = validate_muc(Service, Name),
×
2360
    find_services(Service2).
×
2361

2362
find_services(Global) when Global == global;
2363
                        Global == <<"global">> ->
2364
    lists:flatmap(
×
2365
      fun(ServerHost) ->
2366
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2367
                  true ->
2368
                      [find_service(ServerHost)];
×
2369
                  false ->
2370
                      []
×
2371
              end
2372
      end, ejabberd_option:hosts());
2373
find_services(Service) when is_binary(Service) ->
2374
    [Service].
×
2375

2376
get_room_serverhost(Service) when is_binary(Service) ->
UNCOV
2377
  ejabberd_router:host_of_route(Service).
9✔
2378

2379
find_host(ServerHost) ->
UNCOV
2380
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
42✔
2381

2382
find_hosts(Global) when Global == global;
2383
                        Global == <<"global">> ->
2384
    lists:flatmap(
×
2385
      fun(ServerHost) ->
2386
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2387
                  true ->
2388
                      [find_host(ServerHost)];
×
2389
                  false ->
2390
                      []
×
2391
              end
2392
      end, ejabberd_option:hosts());
2393
find_hosts(ServerHost) ->
UNCOV
2394
    case gen_mod:is_loaded(ServerHost, mod_muc) of
42✔
2395
        true ->
UNCOV
2396
            [find_host(ServerHost)];
42✔
2397
        false ->
2398
            []
×
2399
    end.
2400

2401
mod_opt_type(subscribe_room_many_max_users) ->
2402
    econf:int().
108✔
2403

2404
mod_options(_) ->
2405
    [{subscribe_room_many_max_users, 50}].
108✔
2406

2407
mod_doc() ->
2408
    #{desc =>
×
2409
          [?T("This module provides commands to administer local MUC "
2410
              "services and their MUC rooms. It also provides simple "
2411
              "WebAdmin pages to view the existing rooms."), "",
2412
           ?T("This module depends on _`mod_muc`_.")],
2413
    opts =>
2414
          [{subscribe_room_many_max_users,
2415
            #{value => ?T("Number"),
2416
              note => "added in 22.05",
2417
              desc =>
2418
                  ?T("How many users can be subscribed to a room at once using "
2419
                     "the _`subscribe_room_many`_ API. "
2420
                     "The default value is '50'.")}}]}.
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc