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

processone / ejabberd / 1258

12 Dec 2025 03:57PM UTC coverage: 33.638% (-0.006%) from 33.644%
1258

push

github

badlop
Container: Apply commit a22c88a

ejabberdctl.template: Show meaningful error when ERL_DIST_PORT is in use

15554 of 46240 relevant lines covered (33.64%)

1078.28 hits per line

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

4.01
/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-2025   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]).
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_by_regex, tags = [muc],
111
                       desc = "List existing rooms filtered by regexp",
112
                       longdesc = "Ask for a specific host, or `global` to use all vhosts.",
113
                       policy = admin,
114
                       module = ?MODULE, function = muc_online_rooms_by_regex,
115
                       args_desc = ["MUC service, or `global` for all",
116
                                    "Regex pattern for room name"],
117
                       args_example = ["conference.example.com", "^prefix"],
118
                       result_desc = "List of rooms with summary",
119
                       result_example = [{"room1@conference.example.com", "true", 10},
120
                                         {"room2@conference.example.com", "false", 10}],
121
                       args = [{service, binary}, {regex, binary}],
122
                       args_rename = [{host, service}],
123
                       result = {rooms, {list, {room, {tuple,
124
                                                          [{jid, string},
125
                                                           {public, string},
126
                                                           {participants, integer}
127
                                                          ]}}}}},
128
     #ejabberd_commands{name = muc_register_nick, tags = [muc],
129
                       desc = "Register a nick to a User JID in a MUC service",
130
                       module = ?MODULE, function = muc_register_nick,
131
                       args_desc = ["Nick", "User JID", "Service"],
132
                       args_example = [<<"Tim">>, <<"tim@example.org">>, <<"conference.example.org">>],
133
                       args = [{nick, binary}, {jid, binary}, {service, binary}],
134
                       args_rename = [{host, service}],
135
                       result = {res, rescode}},
136
     #ejabberd_commands{name = muc_register_nick, tags = [muc],
137
                       desc = "Register a nick to a User JID in a MUC service",
138
                       module = ?MODULE, function = muc_register_nick,
139
                       version = 3,
140
                       note = "updated in 24.12",
141
                       args_desc = ["nick", "user name", "user host", "MUC service"],
142
                       args_example = [<<"Tim">>, <<"tim">>, <<"example.org">>, <<"conference.example.org">>],
143
                       args = [{nick, binary}, {user, binary}, {host, binary}, {service, binary}],
144
                       args_rename = [{host, service}],
145
                       result = {res, rescode}},
146
     #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
147
                       desc = "Unregister the nick registered by that account in the MUC service",
148
                       module = ?MODULE, function = muc_unregister_nick,
149
                       args_desc = ["User JID", "MUC service"],
150
                       args_example = [<<"tim@example.org">>, <<"conference.example.org">>],
151
                       args = [{jid, binary}, {service, binary}],
152
                       args_rename = [{host, service}],
153
                       result = {res, rescode}},
154
     #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
155
                       desc = "Unregister the nick registered by that account in the MUC service",
156
                       module = ?MODULE, function = muc_unregister_nick,
157
                       version = 3,
158
                       note = "updated in 24.12",
159
                       args_desc = ["user name", "user host", "MUC service"],
160
                       args_example = [<<"tim">>, <<"example.org">>, <<"conference.example.org">>],
161
                       args = [{user, binary}, {host, binary}, {service, binary}],
162
                       args_rename = [{host, service}],
163
                       result = {res, rescode}},
164
     #ejabberd_commands{name = muc_get_registered_nick, tags = [muc],
165
                       desc = "Get nick registered for that account in the MUC service",
166
                       module = ?MODULE, function = muc_get_registered_nick,
167
                       note = "added in 25.10",
168
                       args_desc = ["user name", "user host", "MUC service"],
169
                       args_example = [<<"tim">>, <<"example.org">>, <<"conference.example.org">>],
170
                       args = [{user, binary}, {host, binary}, {service, binary}],
171
                       result_desc = "nick registered",
172
                       result_example = ["Tim"],
173
                       result = {nick, string}},
174
     #ejabberd_commands{name = muc_get_registered_nicks, tags = [muc],
175
                       desc = "List all nicks registered in the MUC service",
176
                       module = ?MODULE, function = muc_get_registered_nicks,
177
                       note = "added in 25.10",
178
                       args_desc = ["MUC service"],
179
                       args_example = [<<"conference.example.org">>],
180
                       args = [{service, binary}],
181
                       result_example = [{"Tim", "timexa", "example.com"},
182
                                         {"Laia", "laia001", "example2.org"}],
183
                       result = {registrations, {list, {registration, {tuple,
184
                                                          [{user, string},
185
                                                           {host, string},
186
                                                           {nick, string}
187
                                                          ]}}}}},
188

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

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

313
     #ejabberd_commands{name = get_user_rooms, tags = [muc],
314
                        desc = "Get the list of rooms where this user is occupant",
315
                        module = ?MODULE, function = get_user_rooms,
316
                        args_desc = ["Username", "Server host"],
317
                        args_example = ["tom", "example.com"],
318
                        result_example = ["room1@conference.example.com", "room2@conference.example.com"],
319
                        args = [{user, binary}, {host, binary}],
320
                        result = {rooms, {list, {room, string}}}},
321
     #ejabberd_commands{name = get_user_subscriptions, tags = [muc, muc_sub],
322
                        desc = "Get the list of rooms where this user is subscribed",
323
                        note = "added in 21.04",
324
                        module = ?MODULE, function = get_user_subscriptions,
325
                        args_desc = ["Username", "Server host"],
326
                        args_example = ["tom", "example.com"],
327
                        result_example = [{"room1@conference.example.com", "Tommy", ["mucsub:config"]}],
328
                        args = [{user, binary}, {host, binary}],
329
                        result = {rooms,
330
                                  {list,
331
                                   {room,
332
                                    {tuple,
333
                                     [{roomjid, string},
334
                                      {usernick, string},
335
                                      {nodes, {list, {node, string}}}
336
                                     ]}}
337
                                  }}},
338

339
     #ejabberd_commands{name = get_room_occupants, tags = [muc_room],
340
                        desc = "Get the list of occupants of a MUC room",
341
                        module = ?MODULE, function = get_room_occupants,
342
                        args_desc = ["Room name", "MUC service"],
343
                        args_example = ["room1", "conference.example.com"],
344
                        result_desc = "The list of occupants with JID, nick and affiliation",
345
                        result_example = [{"user1@example.com/psi", "User 1", "owner"}],
346
                        args = [{room, binary}, {service, binary}],
347
                        args_rename = [{name, room}],
348
                        result = {occupants, {list,
349
                                              {occupant, {tuple,
350
                                                          [{jid, string},
351
                                                           {nick, string},
352
                                                           {role, string}
353
                                                          ]}}
354
                                             }}},
355

356
     #ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
357
                        desc = "Get the number of occupants of a MUC room",
358
                        module = ?MODULE, function = get_room_occupants_number,
359
                        args_desc = ["Room name", "MUC service"],
360
                        args_example = ["room1", "conference.example.com"],
361
                        result_desc = "Number of room occupants",
362
                        result_example = 7,
363
                        args = [{room, binary}, {service, binary}],
364
                        args_rename = [{name, room}],
365
                        result = {occupants, integer}},
366

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

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

589

590
     #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
591
                        desc = "Get the list of affiliations of a MUC room",
592
                        module = ?MODULE, function = get_room_affiliations,
593
                        args_desc = ["Room name", "MUC service"],
594
                        args_example = ["room1", "conference.example.com"],
595
                        result_desc = "The list of affiliations with username, domain, affiliation and reason",
596
                        result_example = [{"user1", "example.com", member, "member"}],
597
                        args = [{name, binary}, {service, binary}],
598
                        result = {affiliations, {list,
599
                                                 {affiliation, {tuple,
600
                                                                [{username, string},
601
                                                                 {domain, string},
602
                                                                 {affiliation, atom},
603
                                                                 {reason, string}
604
                                                                ]}}
605
                                                }}},
606
     #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
607
                        desc = "Get the list of affiliations of a MUC room",
608
                        module = ?MODULE, function = get_room_affiliations_v3,
609
                        version = 3,
610
                        note = "updated in 24.12",
611
                        args_desc = ["Room name", "MUC service"],
612
                        args_example = ["room1", "conference.example.com"],
613
                        result_desc = "The list of affiliations with jid, affiliation and reason",
614
                        result_example = [{"user1@example.com", member, "member"}],
615
                        args = [{room, binary}, {service, binary}],
616
                        result = {affiliations, {list,
617
                                                 {affiliation, {tuple,
618
                                                                [{jid, string},
619
                                                                 {affiliation, atom},
620
                                                                 {reason, string}
621
                                                                ]}}
622
                                                }}},
623

624

625
         #ejabberd_commands{name = get_room_affiliation, tags = [muc_room],
626
                        desc = "Get affiliation of a user in MUC room",
627
                        module = ?MODULE, function = get_room_affiliation,
628
                        args_desc = ["Room name", "MUC service", "User JID"],
629
                        args_example = ["room1", "conference.example.com", "user1@example.com"],
630
                        result_desc = "Affiliation of the user",
631
                        result_example = member,
632
                        args = [{room, binary}, {service, binary}, {jid, binary}],
633
                        args_rename = [{name, room}],
634
                        result = {affiliation, atom}},
635
         #ejabberd_commands{name = get_room_history, tags = [muc_room],
636
                        desc = "Get history of messages stored inside MUC room state",
637
                        note = "added in 23.04",
638
                        module = ?MODULE, function = get_room_history,
639
                        args_desc = ["Room name", "MUC service"],
640
                        args_example = ["room1", "conference.example.com"],
641
                        args = [{room, binary}, {service, binary}],
642
                        args_rename = [{name, room}],
643
                        result = {history, {list,
644
                                            {entry, {tuple,
645
                                                     [{timestamp, string},
646
                                                      {message, string}]}}}}},
647

648
         #ejabberd_commands{name = webadmin_muc, tags = [internal],
649
                        desc = "Generate WebAdmin MUC Rooms HTML",
650
                        module = ?MODULE, function = webadmin_muc,
651
                        args = [{request, any}, {lang, binary}],
652
                        result = {res, any}}
653
        ].
654

655

656
%%%
657
%%% ejabberd commands
658
%%%
659

660
muc_online_rooms(ServiceArg) ->
661
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
662
    lists:flatmap(
×
663
      fun(Host) ->
664
              [<<Name/binary, "@", Host/binary>>
×
665
               || {Name, _, _} <- mod_muc:get_online_rooms(Host)]
×
666
      end, Hosts).
667

668
muc_online_rooms_by_regex(ServiceArg, Regex) ->
669
    {_, P} = re:compile(Regex),
×
670
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
671
    lists:flatmap(
×
672
      fun(Host) ->
673
              [build_summary_room(Name, RoomHost, Pid)
×
674
               || {Name, RoomHost, Pid} <- mod_muc:get_online_rooms(Host),
×
675
                   is_name_match(Name, P)]
×
676
      end, Hosts).
677

678
is_name_match(Name, P) ->
679
        case re:run(Name, P) of
×
680
                {match, _} -> true;
×
681
                nomatch -> false
×
682
        end.
683

684
build_summary_room(Name, Host, Pid) ->
685
    C = get_room_config(Pid),
×
686
    Public = C#config.public,
×
687
    S = get_room_state(Pid),
×
688
    Participants = maps:size(S#state.users),
×
689
    {<<Name/binary, "@", Host/binary>>,
×
690
         misc:atom_to_binary(Public),
691
     Participants
692
    }.
693

694
muc_register_nick(Nick, User, Host, Service) ->
695
    muc_register_nick(Nick, makeencode(User, Host), Service).
×
696

697
muc_register_nick(Nick, FromBinary, Service) ->
698
    try {get_room_serverhost(Service), jid:decode(FromBinary)} of
×
699
        {ServerHost, From} ->
700
            Lang = <<"en">>,
×
701
            case mod_muc:iq_set_register_info(ServerHost, Service, From, Nick, Lang) of
×
702
                {result, undefined} -> ok;
×
703
                {error, #stanza_error{reason = 'conflict'}} ->
704
                    throw({error, "Nick already registered"});
×
705
                {error, _} ->
706
                    throw({error, "Database error"})
×
707
            end
708
        catch
709
        error:{invalid_domain, _} ->
710
            throw({error, "Invalid value of 'service'"});
×
711
        error:{unregistered_route, _} ->
712
            throw({error, "Unknown host in 'service'"});
×
713
        error:{bad_jid, _} ->
714
            throw({error, "Invalid 'jid'"});
×
715
        _ ->
716
            throw({error, "Internal error"})
×
717
    end.
718

719
muc_unregister_nick(User, Host, Service) ->
720
    muc_unregister_nick(makeencode(User, Host), Service).
×
721

722
muc_unregister_nick(FromBinary, Service) ->
723
    muc_register_nick(<<"">>, FromBinary, Service).
×
724

725
muc_get_registered_nick(User, Host, Service) ->
726
    MucServerHost = get_room_serverhost(Service),
×
727
    case mod_muc:get_register_nick(MucServerHost, Service, jid:make(User, Host)) of
×
728
        error -> <<"">>;
×
729
        N -> N
×
730
    end.
731

732
muc_get_registered_nicks(Service) ->
733
    MucServerHost = get_room_serverhost(Service),
×
734
    mod_muc:get_register_nicks(MucServerHost, Service).
×
735

736
get_user_rooms(User, Server) ->
737
    lists:flatmap(
×
738
      fun(ServerHost) ->
739
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
740
                  true ->
741
                      Rooms = mod_muc:get_online_rooms_by_user(
×
742
                                ServerHost, jid:nodeprep(User), jid:nodeprep(Server)),
743
                      [<<Name/binary, "@", Host/binary>>
×
744
                           || {Name, Host} <- Rooms];
×
745
                  false ->
746
                      []
×
747
              end
748
      end, ejabberd_option:hosts()).
749

750
get_user_subscriptions(User, Server) ->
751
    User2 = validate_user(User, <<"user">>),
×
752
    Server2 = validate_host(Server, <<"host">>),
×
753
    Services = find_services(global),
×
754
    UserJid = jid:make(User2, Server2),
×
755
    lists:flatmap(
×
756
      fun(ServerHost) ->
757
              {ok, Rooms} = mod_muc:get_subscribed_rooms(ServerHost, UserJid),
×
758
              [{jid:encode(RoomJid), UserNick, Nodes}
×
759
               || {RoomJid, UserNick, Nodes} <- Rooms]
×
760
      end, Services).
761

762
%%----------------------------
763
%% Ad-hoc commands
764
%%----------------------------
765

766

767
%%----------------------------
768
%% Web Admin
769
%%----------------------------
770

771
%% @format-begin
772

773
%%---------------
774
%% Web Admin Menu
775

776
web_menu_main(Acc, Lang) ->
777
    Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}].
48✔
778

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

782
%%---------------
783
%% Web Admin Page
784

785
web_page_main(_, #request{path = [<<"muc">>], lang = Lang} = R) ->
786
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
787
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
788
    Res = [make_command(webadmin_muc, R, [{<<"request">>, R}, {<<"lang">>, Lang}], [])],
×
789
    {stop, Title ++ Res};
×
790
web_page_main(Acc, _) ->
791
    Acc.
×
792

793
web_page_host(_, Host, #request{path = [<<"muc">> | RPath], lang = Lang} = R) ->
794
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
795
    Service = find_service(Host),
×
796
    Level = length(RPath),
×
797
    Res = webadmin_muc_host(Host, Service, RPath, R, Lang, Level, PageTitle),
×
798
    {stop, Res};
×
799
web_page_host(Acc, _, _) ->
800
    Acc.
×
801

802
%%---------------
803
%% WebAdmin MUC Host Page
804

805
webadmin_muc_host(Host,
806
                  Service,
807
                  [<<"create-room">> | RPath],
808
                  R,
809
                  _Lang,
810
                  Level,
811
                  PageTitle) ->
812
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
813
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Create Room">>, RPath}),
×
814
    Set = [make_command(create_room, R, [{<<"service">>, Service}, {<<"host">>, Host}], []),
×
815
           make_command(create_room_with_opts,
816
                        R,
817
                        [{<<"service">>, Service}, {<<"host">>, Host}],
818
                        [])],
819
    Title ++ Breadcrumb ++ Set;
×
820
webadmin_muc_host(_Host,
821
                  Service,
822
                  [<<"nick-register">> | RPath],
823
                  R,
824
                  _Lang,
825
                  Level,
826
                  PageTitle) ->
827
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
828
    Breadcrumb =
×
829
        make_breadcrumb({service_section, Level, Service, <<"Nick Register">>, RPath}),
830

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

1077
make_breadcrumb({service, Service}) ->
1078
    make_breadcrumb([Service]);
×
1079
make_breadcrumb({service_section, Level, Service, Section, RPath}) ->
1080
    make_breadcrumb([{Level, Service}, separator, Section | RPath]);
×
1081
make_breadcrumb({room, Level, Service, Name}) ->
1082
    make_breadcrumb([{Level, Service},
×
1083
                     separator,
1084
                     {Level - 1, <<"Rooms">>},
1085
                     separator,
1086
                     jid:encode({Name, Service, <<"">>})]);
1087
make_breadcrumb({room_section, Level, Service, Section, Name, R, RPath}) ->
1088
    make_breadcrumb([{Level, Service},
×
1089
                     separator,
1090
                     {Level - 1, <<"Rooms">>},
1091
                     separator,
1092
                     make_command(echo,
1093
                                  R,
1094
                                  [{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
1095
                                  [{only, value},
1096
                                   {result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
1097
                     separator,
1098
                     Section
1099
                     | RPath]);
1100
make_breadcrumb(Elements) ->
1101
    lists:map(fun ({xmlel, _, _, _} = Xmlel) ->
×
1102
                      Xmlel;
×
1103
                  (<<"sort">>) ->
1104
                      ?C(<<" +">>);
×
1105
                  (<<"page">>) ->
1106
                      ?C(<<" #">>);
×
1107
                  (separator) ->
1108
                      ?C(<<" > ">>);
×
1109
                  (Bin) when is_binary(Bin) ->
1110
                      ?C(Bin);
×
1111
                  ({Level, Bin}) when is_integer(Level) and is_binary(Bin) ->
1112
                      ?AC(binary:copy(<<"../">>, Level), Bin)
×
1113
              end,
1114
              Elements).
1115

1116
%%---------------
1117
%%
1118

1119
%% Returns: {normal | reverse, Integer}
1120
get_sort_query(Q) ->
1121
    case catch get_sort_query2(Q) of
×
1122
        {ok, Res} ->
1123
            Res;
×
1124
        _ ->
1125
            {normal, 1}
×
1126
    end.
1127

1128
get_sort_query2(Q) ->
1129
    {value, {_, Binary}} = lists:keysearch(<<"sort">>, 1, Q),
×
1130
    Integer = list_to_integer(string:strip(binary_to_list(Binary), right, $/)),
×
1131
    case Integer >= 0 of
×
1132
        true ->
1133
            {ok, {normal, Integer}};
×
1134
        false ->
1135
            {ok, {reverse, abs(Integer)}}
×
1136
    end.
1137

1138
webadmin_muc(#request{q = Q} = R, Lang) ->
1139
    {Sort_direction, Sort_column} = get_sort_query(Q),
×
1140
    Host = global,
×
1141
    Service = find_service(Host),
×
1142
    Rooms_names = get_online_rooms(Service),
×
1143
    Rooms_infos = build_info_rooms(Rooms_names),
×
1144
    Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
×
1145
    Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
×
1146
    TList =
×
1147
        lists:map(fun([RoomJid | Room]) ->
1148
                     JidLink =
×
1149
                         make_command(echo,
1150
                                      R,
1151
                                      [{<<"sentence">>, RoomJid}],
1152
                                      [{only, value},
1153
                                       {result_links, [{sentence, room, 1, <<"">>}]}]),
1154
                     ?XE(<<"tr">>, [?XE(<<"td">>, [JidLink]) | [?XC(<<"td">>, E) || E <- Room]])
×
1155
                  end,
1156
                  Rooms_prepared),
1157
    Titles =
×
1158
        [?T("Jabber ID"),
1159
         ?T("# participants"),
1160
         ?T("Last message"),
1161
         ?T("Public"),
1162
         ?T("Persistent"),
1163
         ?T("Logging"),
1164
         ?T("Just created"),
1165
         ?T("Room title"),
1166
         ?T("Node")],
1167
    {Titles_TR, _} =
×
1168
        lists:mapfoldl(fun(Title, Num_column) ->
1169
                          NCS = integer_to_binary(Num_column),
×
1170
                          TD = ?XE(<<"td">>,
×
1171
                                   [?CT(Title),
1172
                                    ?C(<<" ">>),
1173
                                    ?AC(<<"?sort=", NCS/binary>>, <<"<">>),
1174
                                    ?C(<<" ">>),
1175
                                    ?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
1176
                          {TD, Num_column + 1}
×
1177
                       end,
1178
                       1,
1179
                       Titles),
1180
    [?XCT(<<"h2">>, ?T("Chatrooms")),
×
1181
     ?XE(<<"table">>,
1182
         [?XE(<<"thead">>, [?XE(<<"tr">>, Titles_TR)]), ?XE(<<"tbody">>, TList)])].
1183

1184
sort_rooms(Direction, Column, Rooms) ->
1185
    Rooms2 = lists:keysort(Column, Rooms),
×
1186
    case Direction of
×
1187
        normal ->
1188
            Rooms2;
×
1189
        reverse ->
1190
            lists:reverse(Rooms2)
×
1191
    end.
1192

1193
build_info_rooms(Rooms) ->
1194
    [build_info_room(Room) || Room <- Rooms].
×
1195

1196
build_info_room({Name, Host, _ServerHost, Pid}) ->
1197
    C = get_room_config(Pid),
×
1198
    Title = C#config.title,
×
1199
    Public = C#config.public,
×
1200
    Persistent = C#config.persistent,
×
1201
    Logging = C#config.logging,
×
1202

1203
    S = get_room_state(Pid),
×
1204
    Just_created = S#state.just_created,
×
1205
    Num_participants = maps:size(S#state.users),
×
1206
    Node = node(Pid),
×
1207

1208
    History = S#state.history#lqueue.queue,
×
1209
    Ts_last_message =
×
1210
        case p1_queue:is_empty(History) of
1211
            true ->
1212
                <<"A long time ago">>;
×
1213
            false ->
1214
                Last_message1 = get_queue_last(History),
×
1215
                {_, _, _, Ts_last, _} = Last_message1,
×
1216
                xmpp_util:encode_timestamp(Ts_last)
×
1217
        end,
1218

1219
    {<<Name/binary, "@", Host/binary>>,
×
1220
     Num_participants,
1221
     Ts_last_message,
1222
     Public,
1223
     Persistent,
1224
     Logging,
1225
     Just_created,
1226
     Title,
1227
     Node}.
1228

1229
get_queue_last(Queue) ->
1230
    List = p1_queue:to_list(Queue),
×
1231
    lists:last(List).
×
1232

1233
prepare_rooms_infos(Rooms) ->
1234
    [prepare_room_info(Room) || Room <- Rooms].
×
1235

1236
prepare_room_info(Room_info) ->
1237
    {NameHost,
×
1238
     Num_participants,
1239
     Ts_last_message,
1240
     Public,
1241
     Persistent,
1242
     Logging,
1243
     Just_created,
1244
     Title,
1245
     Node} =
1246
        Room_info,
1247
    [NameHost,
×
1248
     integer_to_binary(Num_participants),
1249
     Ts_last_message,
1250
     misc:atom_to_binary(Public),
1251
     misc:atom_to_binary(Persistent),
1252
     misc:atom_to_binary(Logging),
1253
     justcreated_to_binary(Just_created),
1254
     Title,
1255
     misc:atom_to_binary(Node)].
1256

1257
justcreated_to_binary(J) when is_integer(J) ->
1258
    JNow = misc:usec_to_now(J),
×
1259
    {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(JNow),
×
1260
    str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
×
1261
               [Year, Month, Day, Hour, Minute, Second]);
1262
justcreated_to_binary(J) when is_atom(J) ->
1263
    misc:atom_to_binary(J).
×
1264

1265
%%--------------------
1266
%% Web Admin Host User
1267

1268
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
1269
    Acc
1270
    ++ [{<<"muc-rooms">>, <<"MUC Rooms Online">>},
×
1271
        {<<"muc-affiliations">>, <<"MUC Rooms Affiliations">>},
1272
        {<<"muc-sub">>, <<"MUC Rooms Subscriptions">>},
1273
        {<<"muc-register">>, <<"MUC Service Registration">>}].
1274

1275
web_page_hostuser(_, Host, User, #request{path = [<<"muc-rooms">> | RPath]} = R) ->
1276
    Level = 5 + length(RPath),
×
1277
    Res = ?H1GL(<<"MUC Rooms Online">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1278
          ++ [make_command(get_user_rooms,
1279
                           R,
1280
                           [{<<"user">>, User}, {<<"host">>, Host}],
1281
                           [{table_options, {2, RPath}},
1282
                            {result_links, [{room, room, Level, <<"">>}]}])],
1283
    {stop, Res};
×
1284
web_page_hostuser(_, Host, User, #request{path = [<<"muc-affiliations">>]} = R) ->
1285
    Jid = jid:encode(
×
1286
              jid:make(User, Host)),
1287
    Res = ?H1GL(<<"MUC Rooms Affiliations">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1288
          ++ [make_command(set_room_affiliation, R, [{<<"jid">>, Jid}], []),
1289
              make_command(get_room_affiliation, R, [{<<"jid">>, Jid}], [])],
1290
    {stop, Res};
×
1291
web_page_hostuser(_, Host, User, #request{path = [<<"muc-sub">> | RPath]} = R) ->
1292
    Title =
×
1293
        ?H1GLraw(<<"MUC Rooms Subscriptions">>,
1294
                 <<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
1295
                 <<"MUC/Sub">>),
1296
    Level = 5 + length(RPath),
×
1297
    Set = [make_command(subscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
×
1298
           make_command(unsubscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
1299
    Get = [make_command(get_user_subscriptions,
×
1300
                        R,
1301
                        [{<<"user">>, User}, {<<"host">>, Host}],
1302
                        [{table_options, {20, RPath}},
1303
                         {result_links, [{roomjid, room, Level, <<"">>}]}])],
1304
    {stop, Title ++ Get ++ Set};
×
1305
web_page_hostuser(_, Host, User, #request{path = [<<"muc-register">>]} = R) ->
1306
    Jid = jid:encode(
×
1307
              jid:make(User, Host)),
1308
    Res = ?H1GL(<<"MUC Service Registration">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1309
          ++ [make_command(muc_register_nick, R, [{<<"jid">>, Jid}], []),
1310
              make_command(muc_unregister_nick, R, [{<<"jid">>, Jid}], [])],
1311
    {stop, Res};
×
1312
web_page_hostuser(Acc, _, _, _) ->
1313
    Acc.
×
1314
%% @format-end
1315

1316
%%--------------------
1317
%% Web Admin Nick Register
1318

1319
%% @format-begin
1320
make_webadmin_roster_table(Service, R, RPath) ->
1321
    Nicks =
×
1322
        case make_command_raw_value(muc_get_registered_nicks, R, [{<<"service">>, Service}]) of
1323
            Ns when is_list(Ns) ->
1324
                Ns;
×
1325
            _ ->
1326
                []
×
1327
        end,
1328
    Level = 4 + length(RPath),
×
1329
    Columns = [<<"user@host">>, <<"nick">>, <<"">>],
×
1330
    Rows =
×
1331
        lists:map(fun({User, Host, Nick}) ->
1332
                     {make_command(echo,
×
1333
                                   R,
1334
                                   [{<<"sentence">>,
1335
                                     jid:encode(
1336
                                         jid:make(User, Host))}],
1337
                                   [{only, raw_and_value},
1338
                                    {result_links, [{sentence, user, Level, <<"">>}]}]),
1339
                      ?C(Nick),
1340
                      make_command(muc_unregister_nick,
1341
                                   R,
1342
                                   [{<<"user">>, User},
1343
                                    {<<"host">>, Host},
1344
                                    {<<"service">>, Service}],
1345
                                   [{only, button},
1346
                                    {style, danger},
1347
                                    {input_name_append, [User, Host, Service]}])}
1348
                  end,
1349
                  lists:keysort(1, Nicks)),
1350
    Table = make_table(20, RPath, Columns, Rows),
×
1351
    ?XE(<<"blockquote">>, [Table]).
×
1352
%% @format-end
1353

1354

1355
%%----------------------------
1356
%% Create/Delete Room
1357
%%----------------------------
1358

1359
-spec create_room(Name::binary(), Host::binary(), ServerHost::binary()) -> ok | error.
1360
%% @doc Create a room immediately with the default options.
1361
create_room(Name1, Host1, ServerHost) ->
1362
    create_room_with_opts(Name1, Host1, ServerHost, []).
×
1363

1364
create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
1365
    ServerHost = validate_host(ServerHost1, <<"serverhost">>),
×
1366
    case get_room_pid_validate(Name1, Host1, <<"service">>) of
×
1367
        {room_not_found, Name, Host} ->
1368
            %% Get the default room options from the muc configuration
1369
            DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
×
1370
            %% Change default room options as required
1371
            FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
×
1372
            RoomOpts = lists:ukeymerge(1,
×
1373
                lists:keysort(1, FormattedRoomOpts),
1374
                lists:keysort(1, DefRoomOpts)),
1375
            case mod_muc:create_room(Host, Name, RoomOpts) of
×
1376
                ok ->
1377
                    ok;
×
1378
                {error, _} ->
1379
                    throw({error, "Unable to start room"})
×
1380
            end;
1381
        {db_failure, _Name, _Host} ->
1382
            throw({error, "Database error"});
×
1383
        _ ->
1384
            throw({error, "Room already exists"})
×
1385
    end.
1386

1387
%% Create the room only in the database.
1388
%% It is required to restart the MUC service for the room to appear.
1389
muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
1390
    io:format("Creating room ~ts@~ts~n", [Name, Host]),
×
1391
    mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts).
×
1392

1393
-spec destroy_room(Name::binary(), Host::binary()) -> ok | {error, room_not_exists}.
1394
%% @doc Destroy the room immediately.
1395
%% If the room has participants, they are not notified that the room was destroyed;
1396
%% they will notice when they try to chat and receive an error that the room doesn't exist.
1397
destroy_room(Name1, Service1) ->
1398
    case get_room_pid_validate(Name1, Service1, <<"service">>) of
×
1399
        {room_not_found, _, _} ->
1400
            throw({error, "Room doesn't exists"});
×
1401
        {db_failure, _Name, _Host} ->
1402
            throw({error, "Database error"});
×
1403
        {Pid, _, _} ->
1404
            mod_muc_room:destroy(Pid),
×
1405
            ok
×
1406
    end.
1407

1408
destroy_room({N, H, SH}) ->
1409
    io:format("Destroying room: ~ts@~ts - vhost: ~ts~n", [N, H, SH]),
×
1410
    destroy_room(N, H).
×
1411

1412

1413
%%----------------------------
1414
%% Destroy Rooms in File
1415
%%----------------------------
1416

1417
%% The format of the file is: one chatroom JID per line
1418
%% The file encoding must be UTF-8
1419

1420
destroy_rooms_file(Filename) ->
1421
    {ok, F} = file:open(Filename, [read]),
×
1422
    RJID = read_room(F),
×
1423
    Rooms = read_rooms(F, RJID, []),
×
1424
    file:close(F),
×
1425
    [destroy_room(A) || A <- Rooms],
×
1426
    ok.
×
1427

1428
read_rooms(_F, eof, L) ->
1429
    L;
×
1430
read_rooms(F, no_room, L) ->
1431
    RJID2 = read_room(F),
×
1432
    read_rooms(F, RJID2, L);
×
1433
read_rooms(F, RJID, L) ->
1434
    RJID2 = read_room(F),
×
1435
    read_rooms(F, RJID2, [RJID | L]).
×
1436

1437
read_room(F) ->
1438
    case io:get_line(F, "") of
×
1439
        eof -> eof;
×
1440
        String ->
1441
            case io_lib:fread("~ts", String) of
×
1442
                {ok, [RoomJID], _} -> split_roomjid(list_to_binary(RoomJID));
×
1443
                {error, What} ->
1444
                    io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
×
1445
            end
1446
    end.
1447

1448
%% This function is quite rudimentary
1449
%% and may not be accurate
1450
split_roomjid(RoomJID) ->
1451
    split_roomjid2(binary:split(RoomJID, <<"@">>)).
×
1452
split_roomjid2([Name, Host]) ->
1453
    [_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
×
1454
    {Name, Host, ServerHost};
×
1455
split_roomjid2(_) ->
1456
    no_room.
×
1457

1458
%%----------------------------
1459
%% Create Rooms in File
1460
%%----------------------------
1461

1462
create_rooms_file(Filename) ->
1463
    {ok, F} = file:open(Filename, [read]),
×
1464
    RJID = read_room(F),
×
1465
    Rooms = read_rooms(F, RJID, []),
×
1466
    file:close(F),
×
1467
    HostsDetails = get_hosts_details(Rooms),
×
1468
    [muc_create_room(HostsDetails, A) || A <- Rooms],
×
1469
    ok.
×
1470

1471
muc_create_room(HostsDetails, {_, Host, _} = RoomTuple) ->
1472
    {_Host, ServerHost, DefRoomOpts} = get_host_details(Host, HostsDetails),
×
1473
    muc_create_room(ServerHost, RoomTuple, DefRoomOpts).
×
1474

1475
get_hosts_details(Rooms) ->
1476
    Hosts = lists:uniq([Host || {_, Host, _} <- Rooms]),
×
1477
    lists:map(fun(H) ->
×
1478
                      SH = get_room_serverhost(H),
×
1479
                      {H, SH, mod_muc_opt:default_room_options(SH)}
×
1480
              end, Hosts).
1481

1482
get_host_details(Host, ServerHostsDetails) ->
1483
    lists:keyfind(Host, 1, ServerHostsDetails).
×
1484

1485
%%---------------------------------
1486
%% List/Delete Unused/Empty Rooms
1487
%%---------------------------------
1488

1489
%%---------------
1490
%% Control
1491

1492
rooms_unused_list(Service, Days) ->
1493
    rooms_report(unused, list, Service, Days).
×
1494
rooms_unused_destroy(Service, Days) ->
1495
    rooms_report(unused, destroy, Service, Days).
×
1496

1497
rooms_empty_list(Service) ->
1498
    rooms_report(empty, list, Service, 0).
×
1499
rooms_empty_destroy(Service) ->
1500
    rooms_report(empty, destroy, Service, 0).
×
1501

1502
rooms_empty_destroy_restuple(Service) ->
1503
    DestroyedRooms = rooms_report(empty, destroy, Service, 0),
×
1504
    NumberBin = integer_to_binary(length(DestroyedRooms)),
×
1505
    {ok, <<"Destroyed rooms: ", NumberBin/binary>>}.
×
1506

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

1512
muc_unused(Method, Action, Service, Last_allowed) ->
1513
    %% Get all required info about all existing rooms
1514
    Rooms_all = get_all_rooms(Service, erlang:system_time(microsecond) - Last_allowed*24*60*60*1000),
×
1515

1516
    %% Decide which ones pass the requirements
1517
    Rooms_pass = decide_rooms(Method, Rooms_all, Last_allowed),
×
1518

1519
    Num_rooms_all = length(Rooms_all),
×
1520
    Num_rooms_pass = length(Rooms_pass),
×
1521

1522
    %% Perform the desired action for matching rooms
1523
    act_on_rooms(Method, Action, Rooms_pass),
×
1524

1525
    {Num_rooms_all, Num_rooms_pass, Rooms_pass}.
×
1526

1527
%%---------------
1528
%% Get info
1529

1530
get_online_rooms(ServiceArg) ->
1531
    Hosts = find_services(ServiceArg),
×
1532
    lists:flatmap(
×
1533
      fun(Host) ->
1534
          ServerHost = get_room_serverhost(Host),
×
1535
          [{RoomName, RoomHost, ServerHost, Pid}
×
1536
           || {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
×
1537
      end, Hosts).
1538

1539
get_all_rooms(ServiceArg, Timestamp) ->
1540
    Hosts = find_services(ServiceArg),
×
1541
    lists:flatmap(
×
1542
      fun(Host) ->
1543
              get_all_rooms2(Host, Timestamp)
×
1544
      end, Hosts).
1545

1546
get_all_rooms2(Host, Timestamp) ->
1547
    ServerHost = ejabberd_router:host_of_route(Host),
×
1548
    OnlineRooms = get_online_rooms(Host),
×
1549
    OnlineMap = lists:foldl(
×
1550
        fun({Room, _, _, _}, Map) ->
1551
            Map#{Room => 1}
×
1552
        end, #{}, OnlineRooms),
1553

1554
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
×
1555
    DbRooms =
×
1556
    case {erlang:function_exported(Mod, get_rooms_without_subscribers, 2),
1557
          erlang:function_exported(Mod, get_hibernated_rooms_older_than, 3)} of
1558
        {_, true} ->
1559
            Mod:get_hibernated_rooms_older_than(ServerHost, Host, Timestamp);
×
1560
        {true, _} ->
1561
            Mod:get_rooms_without_subscribers(ServerHost, Host);
×
1562
        _ ->
1563
            Mod:get_rooms(ServerHost, Host)
×
1564
    end,
1565
    StoredRooms = lists:filtermap(
×
1566
        fun(#muc_room{name_host = {Room, _}, opts = Opts}) ->
1567
            case maps:is_key(Room, OnlineMap) of
×
1568
                true ->
1569
                    false;
×
1570
                _ ->
1571
                    {true, {Room, Host, ServerHost, Opts}}
×
1572
            end
1573
        end, DbRooms),
1574
    OnlineRooms ++ StoredRooms.
×
1575

1576
get_room_config(Room_pid) ->
1577
    {ok, R} = mod_muc_room:get_config(Room_pid),
×
1578
    R.
×
1579

1580
get_room_state(Room_pid) ->
1581
    {ok, R} = mod_muc_room:get_state(Room_pid),
×
1582
    R.
×
1583

1584
%%---------------
1585
%% Decide
1586

1587
decide_rooms(Method, Rooms, Last_allowed) ->
1588
    Decide = fun(R) -> decide_room(Method, R, Last_allowed) end,
×
1589
    lists:filter(Decide, Rooms).
×
1590

1591
decide_room(unused, {_Room_name, _Host, ServerHost, Room_pid}, Last_allowed) ->
1592
    NodeStartTime = erlang:system_time(microsecond) -
×
1593
                    1000000*(erlang:monotonic_time(second)-ejabberd_config:get_node_start()),
1594
    OnlyHibernated = case mod_muc_opt:hibernation_timeout(ServerHost) of
×
1595
        Value when Value < Last_allowed*24*60*60*1000 ->
1596
            true;
×
1597
        _ ->
1598
            false
×
1599
        end,
1600
    {Just_created, Num_users} =
×
1601
    case Room_pid of
1602
        Pid when is_pid(Pid) andalso OnlyHibernated ->
1603
            {erlang:system_time(microsecond), 0};
×
1604
        Pid when is_pid(Pid) ->
1605
            case mod_muc_room:get_state(Room_pid) of
×
1606
                {ok, #state{just_created = JC, users = U}} ->
1607
                    {JC, maps:size(U)};
×
1608
                _ ->
1609
                    {erlang:system_time(microsecond), 0}
×
1610
            end;
1611
        Opts ->
1612
            case lists:keyfind(hibernation_time, 1, Opts) of
×
1613
                false ->
1614
                    {NodeStartTime, 0};
×
1615
                {_, undefined} ->
1616
                    {NodeStartTime, 0};
×
1617
                {_, T} ->
1618
                    {T, 0}
×
1619
            end
1620
    end,
1621
    Last = case Just_created of
×
1622
               true ->
1623
                   0;
×
1624
               _ ->
1625
                   (erlang:system_time(microsecond)
1626
                    - Just_created) div 1000000
×
1627
           end,
1628
    case {Num_users, seconds_to_days(Last)} of
×
1629
        {0, Last_days} when (Last_days >= Last_allowed) ->
1630
            true;
×
1631
        _ ->
1632
            false
×
1633
    end;
1634
decide_room(empty, {Room_name, Host, ServerHost, Room_pid}, _Last_allowed) ->
1635
    case gen_mod:is_loaded(ServerHost, mod_mam) of
×
1636
        true ->
1637
            Room_options = case Room_pid of
×
1638
                               _ when is_pid(Room_pid) ->
1639
                                   get_room_options(Room_pid);
×
1640
                               Opts ->
1641
                                   Opts
×
1642
                           end,
1643
            case lists:keyfind(<<"mam">>, 1, Room_options) of
×
1644
                {<<"mam">>, <<"true">>} ->
1645
                    mod_mam:is_empty_for_room(ServerHost, Room_name, Host);
×
1646
                _ ->
1647
                    false
×
1648
            end;
1649
        _ ->
1650
            false
×
1651
    end.
1652

1653
seconds_to_days(S) ->
1654
    S div (60*60*24).
×
1655

1656
%%---------------
1657
%% Act
1658

1659
act_on_rooms(Method, Action, Rooms) ->
1660
    Delete = fun(Room) ->
×
1661
                     act_on_room(Method, Action, Room)
×
1662
             end,
1663
    lists:foreach(Delete, Rooms).
×
1664

1665
act_on_room(Method, destroy, {N, H, _SH, Pid}) ->
1666
    Message = iolist_to_binary(io_lib:format(
×
1667
        <<"Room destroyed by rooms_~s_destroy.">>, [Method])),
1668
    case Pid of
×
1669
        V when is_pid(V) ->
1670
            mod_muc_room:destroy(Pid, Message);
×
1671
        _ ->
1672
            case get_room_pid(N, H) of
×
1673
                Pid2 when is_pid(Pid2) ->
1674
                    mod_muc_room:destroy(Pid2, Message);
×
1675
                _ ->
1676
                    ok
×
1677
            end
1678
    end;
1679
act_on_room(_Method, list, _) ->
1680
    ok.
×
1681

1682

1683
%%----------------------------
1684
%% Change Room Option
1685
%%----------------------------
1686

1687
get_room_occupants(Room, Host) ->
1688
    case get_room_pid_validate(Room, Host, <<"service">>) of
×
1689
        {Pid, _, _} when is_pid(Pid) -> get_room_occupants(Pid);
×
1690
        _ -> throw({error, room_not_found})
×
1691
    end.
1692

1693
get_room_occupants(Pid) ->
1694
    S = get_room_state(Pid),
×
1695
    lists:map(
×
1696
      fun({_LJID, Info}) ->
1697
              {jid:encode(Info#user.jid),
×
1698
               Info#user.nick,
1699
               atom_to_list(Info#user.role)}
1700
      end,
1701
      maps:to_list(S#state.users)).
1702

1703
get_room_occupants_number(Room, Host) ->
1704
    case get_room_pid_validate(Room, Host, <<"service">>) of
×
1705
        {Pid, _, _} when is_pid(Pid)->
1706
            {ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid),
×
1707
            N;
×
1708
        _ ->
1709
            throw({error, room_not_found})
×
1710
    end.
1711

1712
%%----------------------------
1713
%% Send Direct Invitation
1714
%%----------------------------
1715
%% http://xmpp.org/extensions/xep-0249.html
1716

1717
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) when is_binary(UsersString) ->
1718
    UsersStrings = binary:split(UsersString, <<":">>, [global]),
×
1719
    send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings);
×
1720
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings) ->
1721
    case jid:make(RoomName, RoomService) of
×
1722
        error ->
1723
            throw({error, "Invalid 'roomname' or 'service'"});
×
1724
        RoomJid ->
1725
            XmlEl = build_invitation(Password, Reason, RoomJid),
×
1726
            Users = get_users_to_invite(RoomJid, UsersStrings),
×
1727
            [send_direct_invitation(RoomJid, UserJid, XmlEl)
×
1728
             || UserJid <- Users],
×
1729
            ok
×
1730
    end.
1731

1732
get_users_to_invite(RoomJid, UsersStrings) ->
1733
    OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
×
1734
                                         RoomJid#jid.lserver),
1735
    OccupantsJids = try [jid:decode(JidString)
×
1736
                         || {JidString, _Nick, _} <- OccupantsTuples]
×
1737
                    catch _:{bad_jid, _} -> throw({error, "Malformed JID of invited user"})
×
1738
                    end,
1739
    lists:filtermap(
×
1740
      fun(UserString) ->
1741
              UserJid = jid:decode(UserString),
×
1742
              Val = lists:all(fun(OccupantJid) ->
×
1743
                                      UserJid#jid.luser /= OccupantJid#jid.luser
×
1744
                                          orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
×
1745
                              end,
1746
                              OccupantsJids),
1747
              case {UserJid#jid.luser, Val} of
×
1748
                  {<<>>, _} -> false;
×
1749
                  {_, true} -> {true, UserJid};
×
1750
                  _ -> false
×
1751
              end
1752
      end,
1753
      UsersStrings).
1754

1755
build_invitation(Password, Reason, RoomJid) ->
1756
    Invite = #x_conference{jid = RoomJid,
×
1757
                           password = case Password of
1758
                                          <<"none">> -> <<>>;
×
1759
                                          _ -> Password
×
1760
                                      end,
1761
                           reason = case Reason of
1762
                                        <<"none">> -> <<>>;
×
1763
                                        _ -> Reason
×
1764
                                    end},
1765
    #message{sub_els = [Invite]}.
×
1766

1767
send_direct_invitation(FromJid, UserJid, Msg) ->
1768
    ejabberd_router:route(xmpp:set_from_to(Msg, FromJid, UserJid)).
×
1769

1770
%%----------------------------
1771
%% Change Room Option
1772
%%----------------------------
1773

1774
-spec change_room_option(Name::binary(), Service::binary(), Option::binary(),
1775
                         Value::atom() | integer() | string()) -> ok | mod_muc_log_not_enabled.
1776
%% @doc Change an option in an existing room.
1777
%% Requires the name of the room, the MUC service where it exists,
1778
%% the option to change (for example title or max_users),
1779
%% and the value to assign to the new option.
1780
%% For example:
1781
%% `change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)'
1782
change_room_option(Name, Service, OptionString, ValueString) ->
1783
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
1784
        {room_not_found, _, _} ->
1785
            throw({error, "Room not found"});
×
1786
        {db_failure, _Name, _Host} ->
1787
            throw({error, "Database error"});
×
1788
        {Pid, _, _} ->
1789
            {Option, Value} = format_room_option(OptionString, ValueString),
×
1790
            change_room_option(Pid, Option, Value)
×
1791
    end.
1792

1793
change_room_option(Pid, Option, Value) ->
1794
    case {Option,
×
1795
          gen_mod:is_loaded((get_room_state(Pid))#state.server_host, mod_muc_log)} of
1796
        {logging, false} ->
1797
            mod_muc_log_not_enabled;
×
1798
        _ ->
1799
            Config = get_room_config(Pid),
×
1800
            Config2 = change_option(Option, Value, Config),
×
1801
            {ok, _} = mod_muc_room:set_config(Pid, Config2),
×
1802
            ok
×
1803
    end.
1804

1805
format_room_option(OptionString, ValueString) ->
1806
    Option = misc:binary_to_atom(OptionString),
×
1807
    Value = case Option of
×
1808
                title -> ValueString;
×
1809
                description -> ValueString;
×
1810
                password -> ValueString;
×
1811
                subject ->ValueString;
×
1812
                subject_author ->ValueString;
×
1813
                max_users -> try_convert_integer(Option, ValueString);
×
1814
                voice_request_min_interval -> try_convert_integer(Option, ValueString);
×
1815
                vcard -> ValueString;
×
1816
                vcard_xupdate when ValueString /= <<"undefined">>,
1817
                                   ValueString /= <<"external">> ->
1818
                    ValueString;
×
1819
                lang -> ValueString;
×
1820
                pubsub -> ValueString;
×
1821
                affiliations ->
1822
                    [parse_affiliation_string(Opt) || Opt <- str:tokens(ValueString, <<";,">>)];
×
1823
                subscribers ->
1824
                    [parse_subscription_string(Opt) || Opt <- str:tokens(ValueString, <<";,">>)];
×
1825
                allow_private_messages_from_visitors when
1826
                      (ValueString == <<"anyone">>) or
1827
                      (ValueString == <<"moderators">>) or
1828
                      (ValueString == <<"nobody">>) -> binary_to_existing_atom(ValueString, utf8);
×
1829
                allowpm when
1830
                      (ValueString == <<"anyone">>) or
1831
                      (ValueString == <<"participants">>) or
1832
                      (ValueString == <<"moderators">>) or
1833
                      (ValueString == <<"none">>) -> binary_to_existing_atom(ValueString, utf8);
×
1834
                presence_broadcast when
1835
                      (ValueString == <<"participant">>) or
1836
                      (ValueString == <<"moderator">>) or
1837
                      (ValueString == <<"visitor">>) -> binary_to_existing_atom(ValueString, utf8);
×
1838
                _ when ValueString == <<"true">> -> true;
×
1839
                _ when ValueString == <<"false">> -> false;
×
1840
                _ -> throw_error(Option, ValueString)
×
1841
            end,
1842
    {Option, Value}.
×
1843

1844
try_convert_integer(Option, ValueString) ->
1845
    try binary_to_integer(ValueString) of
×
1846
        I when is_integer(I) -> I
×
1847
    catch _:badarg ->
1848
        throw_error(Option, ValueString)
×
1849
    end.
1850

1851
throw_error(O, V) ->
1852
    throw({error, "Invalid value for that option", O, V}).
×
1853

1854
parse_affiliation_string(String) ->
1855
    {Type, JidS} = case String of
×
1856
                       %% Old syntax
1857
                       <<"owner:", Jid/binary>> -> {owner, Jid};
×
1858
                       <<"admin:", Jid/binary>> -> {admin, Jid};
×
1859
                       <<"member:", Jid/binary>> -> {member, Jid};
×
1860
                       <<"outcast:", Jid/binary>> -> {outcast, Jid};
×
1861
                       %% New syntax
1862
                       <<"owner=", Jid/binary>> -> {owner, Jid};
×
1863
                       <<"admin=", Jid/binary>> -> {admin, Jid};
×
1864
                       <<"member=", Jid/binary>> -> {member, Jid};
×
1865
                       <<"outcast=", Jid/binary>> -> {outcast, Jid};
×
1866
                       _ -> throw({error, "Invalid 'affiliation'"})
×
1867
                   end,
1868
    try jid:decode(JidS) of
×
1869
        #jid{luser = U, lserver = S, lresource = R} ->
1870
            {{U, S, R}, {Type, <<>>}}
×
1871
    catch _:{bad_jid, _} ->
1872
        throw({error, "Malformed JID in affiliation"})
×
1873
    end.
1874

1875
parse_subscription_string(String) ->
1876
    case str:tokens(String, <<"=:">>) of
×
1877
        [_] ->
1878
            throw({error, "Invalid 'subscribers' - missing nick"});
×
1879
        [_, _] ->
1880
            throw({error, "Invalid 'subscribers' - missing nodes"});
×
1881
        [JidS, Nick | Nodes] ->
1882
            Nodes2 = parse_nodes(Nodes, []),
×
1883
            try jid:decode(JidS) of
×
1884
                Jid ->
1885
                    {Jid, Nick, Nodes2}
×
1886
            catch _:{bad_jid, _} ->
1887
                throw({error, "Malformed JID in 'subscribers'"})
×
1888
            end
1889
    end.
1890

1891
parse_nodes([], Acc) ->
1892
    Acc;
×
1893
parse_nodes([<<"presence">> | Rest], Acc) ->
1894
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PRESENCE | Acc]);
×
1895
parse_nodes([<<"messages">> | Rest], Acc) ->
1896
    parse_nodes(Rest, [?NS_MUCSUB_NODES_MESSAGES | Acc]);
×
1897
parse_nodes([<<"participants">> | Rest], Acc) ->
1898
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PARTICIPANTS | Acc]);
×
1899
parse_nodes([<<"affiliations">> | Rest], Acc) ->
1900
    parse_nodes(Rest, [?NS_MUCSUB_NODES_AFFILIATIONS | Acc]);
×
1901
parse_nodes([<<"subject">> | Rest], Acc) ->
1902
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBJECT | Acc]);
×
1903
parse_nodes([<<"config">> | Rest], Acc) ->
1904
    parse_nodes(Rest, [?NS_MUCSUB_NODES_CONFIG | Acc]);
×
1905
parse_nodes([<<"system">> | Rest], Acc) ->
1906
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SYSTEM | Acc]);
×
1907
parse_nodes([<<"subscribers">> | Rest], Acc) ->
1908
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBSCRIBERS | Acc]);
×
1909
parse_nodes(_, _) ->
1910
    throw({error, "Invalid 'subscribers' - unknown node name used"}).
×
1911

1912
-spec get_room_pid_validate(binary(), binary(), binary()) ->
1913
    {pid() | room_not_found | db_failure, binary(), binary()}.
1914
get_room_pid_validate(Name, Service, ServiceArg) ->
1915
    Name2 = validate_room(Name),
9✔
1916
    {ServerHost, Service2} = validate_muc2(Service, ServiceArg),
9✔
1917
    case mod_muc:unhibernate_room(ServerHost, Service2, Name2) of
9✔
1918
        {error, notfound} ->
1919
            {room_not_found, Name2, Service2};
×
1920
        {error, db_failure} ->
1921
            {db_failure, Name2, Service2};
×
1922
        {ok, Pid} ->
1923
            {Pid, Name2, Service2}
9✔
1924
    end.
1925

1926
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
1927
-spec get_room_pid(binary(), binary()) -> pid() | room_not_found | db_failure | invalid_service | unknown_service.
1928
get_room_pid(Name, Service) ->
1929
    try get_room_serverhost(Service) of
×
1930
        ServerHost ->
1931
            case mod_muc:unhibernate_room(ServerHost, Service, Name) of
×
1932
                {error, notfound} ->
1933
                    room_not_found;
×
1934
                {error, db_failure} ->
1935
                    db_failure;
×
1936
                {ok, Pid} ->
1937
                    Pid
×
1938
            end
1939
    catch
1940
        error:{invalid_domain, _} ->
1941
            invalid_service;
×
1942
        error:{unregistered_route, _} ->
1943
            unknown_service
×
1944
    end.
1945

1946
room_diagnostics(Name, Service) ->
1947
    try get_room_serverhost(Service) of
×
1948
        ServerHost ->
1949
            RMod = gen_mod:ram_db_mod(ServerHost, mod_muc),
×
1950
            case RMod:find_online_room(ServerHost, Name, Service) of
×
1951
                error ->
1952
                    room_hibernated;
×
1953
                {ok, Pid} ->
1954
                    case rpc:pinfo(Pid, [current_stacktrace, message_queue_len, messages]) of
×
1955
                        [{_, R}, {_, QL}, {_, Q}] ->
1956
                            #{stacktrace => R, queue_size => QL, queue => lists:sublist(Q, 10)};
×
1957
                        _ ->
1958
                            unable_to_probe_process
×
1959
                    end
1960
            end
1961
    catch
1962
        error:{invalid_domain, _} ->
1963
            invalid_service;
×
1964
        error:{unregistered_route, _} ->
1965
            unknown_service
×
1966
    end.
1967

1968
%% It is required to put explicitly all the options because
1969
%% the record elements are replaced at compile time.
1970
%% So, this can't be parametrized.
1971
change_option(Option, Value, Config) ->
1972
    case Option of
×
1973
        allow_change_subj -> Config#config{allow_change_subj = Value};
×
1974
        allowpm -> Config#config{allowpm = Value};
×
1975
        allow_private_messages_from_visitors -> Config#config{allow_private_messages_from_visitors = Value};
×
1976
        allow_query_users -> Config#config{allow_query_users = Value};
×
1977
        allow_subscription -> Config#config{allow_subscription = Value};
×
1978
        allow_user_invites -> Config#config{allow_user_invites = Value};
×
1979
        allow_visitor_nickchange -> Config#config{allow_visitor_nickchange = Value};
×
1980
        allow_visitor_status -> Config#config{allow_visitor_status = Value};
×
1981
        allow_voice_requests -> Config#config{allow_voice_requests = Value};
×
1982
        anonymous -> Config#config{anonymous = Value};
×
1983
        captcha_protected -> Config#config{captcha_protected = Value};
×
1984
        description -> Config#config{description = Value};
×
1985
        enable_hats -> Config#config{enable_hats = Value};
×
1986
        lang -> Config#config{lang = Value};
×
1987
        logging -> Config#config{logging = Value};
×
1988
        mam -> Config#config{mam = Value};
×
1989
        max_users -> Config#config{max_users = Value};
×
1990
        members_by_default -> Config#config{members_by_default = Value};
×
1991
        members_only -> Config#config{members_only = Value};
×
1992
        moderated -> Config#config{moderated = Value};
×
1993
        password -> Config#config{password = Value};
×
1994
        password_protected -> Config#config{password_protected = Value};
×
1995
        persistent -> Config#config{persistent = Value};
×
1996
        presence_broadcast -> Config#config{presence_broadcast = Value};
×
1997
        public -> Config#config{public = Value};
×
1998
        public_list -> Config#config{public_list = Value};
×
1999
        pubsub -> Config#config{pubsub = Value};
×
2000
        title -> Config#config{title = Value};
×
2001
        vcard -> Config#config{vcard = Value};
×
2002
        vcard_xupdate -> Config#config{vcard_xupdate = Value};
×
2003
        voice_request_min_interval -> Config#config{voice_request_min_interval = Value}
×
2004
    end.
2005

2006
%%----------------------------
2007
%% Get Room Options
2008
%%----------------------------
2009

2010
get_room_options(Name, Service) ->
2011
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2012
        {Pid, _, _} when is_pid(Pid) -> get_room_options(Pid);
×
2013
        _ -> []
×
2014
    end.
2015

2016
get_room_options(Pid) ->
2017
    Config = get_room_config(Pid),
×
2018
    get_options(Config).
×
2019

2020
get_options(Config) ->
2021
    Fields = [misc:atom_to_binary(Field) || Field <- record_info(fields, config)],
×
2022
    [config | ValuesRaw] = tuple_to_list(Config),
×
2023
    Values = lists:map(fun(V) when is_atom(V) -> misc:atom_to_binary(V);
×
2024
                          (V) when is_integer(V) -> integer_to_binary(V);
×
2025
                          (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
×
2026
                          (V) -> V end, ValuesRaw),
×
2027
    lists:zip(Fields, Values).
×
2028

2029
%%----------------------------
2030
%% Get Room Affiliations
2031
%%----------------------------
2032

2033
%% @spec(Name::binary(), Service::binary()) ->
2034
%%    [{Username::string(), Domain::string(), Role::string(), Reason::string()}]
2035
%% @doc Get the affiliations of  the room Name@Service.
2036
get_room_affiliations(Name, Service) ->
2037
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2038
        {Pid, _, _} when is_pid(Pid) ->
2039
            %% Get the PID of the online room, then request its state
2040
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
2041
            Affiliations = maps:to_list(StateData#state.affiliations),
×
2042
            lists:map(
×
2043
              fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
2044
                      {Uname, Domain, Aff, Reason};
×
2045
                 ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
2046
                      {Uname, Domain, Aff, <<>>}
×
2047
              end, Affiliations);
2048
        {db_failure, _Name, _Host} ->
2049
            throw({error, "Database error"});
×
2050
        _ ->
2051
            throw({error, "The room does not exist."})
×
2052
    end.
2053

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

2077
get_room_history(Name, Service) ->
2078
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2079
        {Pid, _, _} when is_pid(Pid) ->
2080
            case mod_muc_room:get_state(Pid) of
×
2081
                {ok, StateData} ->
2082
                    History = p1_queue:to_list((StateData#state.history)#lqueue.queue),
×
2083
                    lists:map(
×
2084
                        fun({_Nick, Packet, _HaveSubject, TimeStamp, _Size}) ->
2085
                            {xmpp_util:encode_timestamp(TimeStamp),
×
2086
                             ejabberd_web_admin:pretty_print_xml(xmpp:encode(Packet))}
2087
                        end, History);
2088
                _ ->
2089
                    throw({error, "Unable to fetch room state."})
×
2090
            end;
2091
        {db_failure, _Name, _Host} ->
2092
            throw({error, "Database error"});
×
2093
        _ ->
2094
            throw({error, "The room does not exist."})
×
2095
    end.
2096

2097
%%----------------------------
2098
%% Get Room Affiliation
2099
%%----------------------------
2100

2101
%% @spec(Name::binary(), Service::binary(), JID::binary()) ->
2102
%%    {Affiliation::string()}
2103
%% @doc Get affiliation of a user in the room Name@Service.
2104

2105
get_room_affiliation(Name, Service, JID) ->
2106
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2107
        {Pid, _, _} when is_pid(Pid) ->
2108
            %% Get the PID of the online room, then request its state
2109
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
2110
            UserJID = jid:decode(JID),
×
2111
            mod_muc_room:get_affiliation(UserJID, StateData);
×
2112
        {db_failure, _Name, _Host} ->
2113
            throw({error, "Database error"});
×
2114
        _ ->
2115
            throw({error, "The room does not exist."})
×
2116
    end.
2117

2118
%%----------------------------
2119
%% Change Room Affiliation
2120
%%----------------------------
2121

2122
set_room_affiliation(Name, Service, User, Host, AffiliationString) ->
2123
    set_room_affiliation(Name, Service, makeencode(User, Host), AffiliationString).
9✔
2124

2125
%% @spec(Name, Service, JID, AffiliationString) -> ok | {error, Error}
2126
%%       Name = binary()
2127
%%       Service = binary()
2128
%%       JID = binary()
2129
%%       AffiliationString = "outcast" | "none" | "member" | "admin" | "owner"
2130
%% @doc Set the affiliation of JID in the room Name@Service.
2131
%% If the affiliation is 'none', the action is to remove,
2132
%% In any other case the action will be to create the affiliation.
2133
set_room_affiliation(Name, Service, JID, AffiliationString) ->
2134
    Affiliation = case AffiliationString of
9✔
2135
                      <<"outcast">> -> outcast;
×
2136
                      <<"none">> -> none;
×
2137
                      <<"member">> -> member;
9✔
2138
                      <<"admin">> -> admin;
×
2139
                      <<"owner">> -> owner;
×
2140
                      _ ->
2141
                          throw({error, "Invalid affiliation"})
×
2142
                  end,
2143
    case get_room_pid_validate(Name, Service, <<"service">>) of
9✔
2144
        {Pid, _, _} when is_pid(Pid) ->
2145
            %% Get the PID for the online room so we can get the state of the room
2146
            case mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>) of
9✔
2147
                {ok, _} ->
2148
                    ok;
9✔
2149
                {error, notfound} ->
2150
                    throw({error, "Room doesn't exists"});
×
2151
                {error, _} ->
2152
                    throw({error, "Unable to perform change"})
×
2153
            end;
2154
        {db_failure, _Name, _Host} ->
2155
            throw({error, "Database error"});
×
2156
        _ ->
2157
            throw({error, "Room doesn't exists"})
×
2158
    end.
2159

2160
%%%
2161
%%% MUC Subscription
2162
%%%
2163

2164
subscribe_room(Username, Host, Nick, Name, Service, Nodes) ->
2165
    subscribe_room(makeencode(Username, Host), Nick,
×
2166
                   makeencode(Name, Service), Nodes).
2167

2168
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
2169
    throw({error, "Nickname must be set"});
×
2170
subscribe_room(User, Nick, Room, Nodes) when is_binary(Nodes) ->
2171
    NodeList = re:split(Nodes, "\\h*,\\h*"),
×
2172
    subscribe_room(User, Nick, Room, NodeList);
×
2173
subscribe_room(User, Nick, Room, NodeList) ->
2174
    try jid:decode(Room) of
×
2175
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
2176
            try jid:decode(User) of
×
2177
                UserJID1 ->
2178
                    UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
×
2179
                    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2180
                        {Pid, _, _} when is_pid(Pid) ->
2181
                            case mod_muc_room:subscribe(
×
2182
                                   Pid, UserJID, Nick, NodeList) of
2183
                                {ok, SubscribedNodes} ->
2184
                                    SubscribedNodes;
×
2185
                                {error, Reason} ->
2186
                                    throw({error, binary_to_list(Reason)})
×
2187
                            end;
2188
                        {db_failure, _Name, _Host} ->
2189
                            throw({error, "Database error"});
×
2190
                        _ ->
2191
                            throw({error, "The room does not exist"})
×
2192
                    end
2193
            catch _:{bad_jid, _} ->
2194
                    throw({error, "Malformed user JID"})
×
2195
            end;
2196
        _ ->
2197
            throw({error, "Malformed room JID"})
×
2198
    catch _:{bad_jid, _} ->
2199
            throw({error, "Malformed room JID"})
×
2200
    end.
2201

2202
subscribe_room_many_v3(List, Name, Service, Nodes) ->
2203
    List2 = [{makeencode(User, Host), Nick} || {User, Host, Nick} <- List],
×
2204
    subscribe_room_many(List2, makeencode(Name, Service), Nodes).
×
2205

2206
subscribe_room_many(Users, Room, Nodes) ->
2207
    MaxUsers = mod_muc_admin_opt:subscribe_room_many_max_users(global),
×
2208
    if
×
2209
        length(Users) > MaxUsers ->
2210
            throw({error, "Too many users in subscribe_room_many command"});
×
2211
        true ->
2212
            lists:foreach(
×
2213
              fun({User, Nick}) ->
2214
                      subscribe_room(User, Nick, Room, Nodes)
×
2215
              end, Users)
2216
    end.
2217

2218
unsubscribe_room(User, Host, Name, Service) ->
2219
    unsubscribe_room(makeencode(User, Host),
×
2220
                     makeencode(Name, Service)).
2221

2222
unsubscribe_room(User, Room) ->
2223
    try jid:decode(Room) of
×
2224
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
2225
            try jid:decode(User) of
×
2226
                UserJID ->
2227
                    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2228
                        {Pid, _, _} when is_pid(Pid) ->
2229
                            case mod_muc_room:unsubscribe(Pid, UserJID) of
×
2230
                                ok ->
2231
                                    ok;
×
2232
                                {error, Reason} ->
2233
                                    throw({error, binary_to_list(Reason)})
×
2234
                            end;
2235
                        {db_failure, _Name, _Host} ->
2236
                            throw({error, "Database error"});
×
2237
                        _ ->
2238
                            throw({error, "The room does not exist"})
×
2239
                    end
2240
            catch _:{bad_jid, _} ->
2241
                    throw({error, "Malformed user JID"})
×
2242
            end;
2243
        _ ->
2244
            throw({error, "Malformed room JID"})
×
2245
    catch _:{bad_jid, _} ->
2246
            throw({error, "Malformed room JID"})
×
2247
    end.
2248

2249
get_subscribers(Name, Host) ->
2250
    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2251
        {Pid, _, _} when is_pid(Pid) ->
2252
            {ok, JIDList} = mod_muc_room:get_subscribers(Pid),
×
2253
            [jid:encode(jid:remove_resource(J)) || J <- JIDList];
×
2254
        {db_failure, _Name, _Host} ->
2255
            throw({error, "Database error"});
×
2256
        _ ->
2257
            throw({error, "The room does not exist"})
×
2258
    end.
2259

2260
%%----------------------------
2261
%% Utils
2262
%%----------------------------
2263

2264
makeencode(User, Host) ->
2265
    jid:encode(jid:make(User, Host)).
9✔
2266

2267
-spec validate_host(Name :: binary(), ArgName::binary()) -> binary().
2268
validate_host(Name, ArgName) ->
2269
    case jid:nameprep(Name) of
×
2270
        error ->
2271
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2272
        Name2 ->
2273
            case lists:member(Name2, ejabberd_option:hosts()) of
×
2274
                false ->
2275
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2276
                _ ->
2277
                    Name2
×
2278
            end
2279
    end.
2280

2281
-spec validate_user(Name :: binary(), ArgName::binary()) -> binary().
2282
validate_user(Name, ArgName) ->
2283
    case jid:nodeprep(Name) of
×
2284
        error ->
2285
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2286
        Name2 ->
2287
            Name2
×
2288
    end.
2289

2290
-spec validate_muc(Name :: binary(), ArgName::binary()) -> binary().
2291
validate_muc(Name, ArgName) ->
2292
    case jid:nameprep(Name) of
×
2293
        error ->
2294
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2295
        Name2 ->
2296
            try get_room_serverhost(Name2) of
×
2297
                _ -> Name2
×
2298
            catch
2299
                error:{invalid_domain, _} ->
2300
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2301
                error:{unregistered_route, _} ->
2302
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
×
2303
            end
2304
    end.
2305

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

2322
-spec validate_room(Name :: binary()) -> binary().
2323
validate_room(Name) ->
2324
    case jid:nodeprep(Name) of
9✔
2325
        error ->
2326
            throw({error, <<"Invalid value of room name">>});
×
2327
        Name2 ->
2328
            Name2
9✔
2329
    end.
2330

2331
find_service(global) ->
2332
    global;
×
2333
find_service(ServerHost) ->
2334
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
×
2335

2336
find_services_validate(Global, _Name) when Global == global;
2337
    Global == <<"global">> ->
2338
    find_services(Global);
×
2339
find_services_validate(Service, Name) ->
2340
    Service2 = validate_muc(Service, Name),
×
2341
    find_services(Service2).
×
2342

2343
find_services(Global) when Global == global;
2344
                        Global == <<"global">> ->
2345
    lists:flatmap(
×
2346
      fun(ServerHost) ->
2347
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2348
                  true ->
2349
                      [find_service(ServerHost)];
×
2350
                  false ->
2351
                      []
×
2352
              end
2353
      end, ejabberd_option:hosts());
2354
find_services(Service) when is_binary(Service) ->
2355
    [Service].
×
2356

2357
get_room_serverhost(Service) when is_binary(Service) ->
2358
  ejabberd_router:host_of_route(Service).
9✔
2359

2360
find_host(ServerHost) ->
2361
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
42✔
2362

2363
find_hosts(Global) when Global == global;
2364
                        Global == <<"global">> ->
2365
    lists:flatmap(
×
2366
      fun(ServerHost) ->
2367
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2368
                  true ->
2369
                      [find_host(ServerHost)];
×
2370
                  false ->
2371
                      []
×
2372
              end
2373
      end, ejabberd_option:hosts());
2374
find_hosts(ServerHost) ->
2375
    case gen_mod:is_loaded(ServerHost, mod_muc) of
42✔
2376
        true ->
2377
            [find_host(ServerHost)];
42✔
2378
        false ->
2379
            []
×
2380
    end.
2381

2382
mod_opt_type(subscribe_room_many_max_users) ->
2383
    econf:int().
108✔
2384

2385
mod_options(_) ->
2386
    [{subscribe_room_many_max_users, 50}].
108✔
2387

2388
mod_doc() ->
2389
    #{desc =>
×
2390
          [?T("This module provides commands to administer local MUC "
2391
              "services and their MUC rooms. It also provides simple "
2392
              "WebAdmin pages to view the existing rooms."), "",
2393
           ?T("This module depends on _`mod_muc`_.")],
2394
    opts =>
2395
          [{subscribe_room_many_max_users,
2396
            #{value => ?T("Number"),
2397
              note => "added in 22.05",
2398
              desc =>
2399
                  ?T("How many users can be subscribed to a room at once using "
2400
                     "the _`subscribe_room_many`_ API. "
2401
                     "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

© 2025 Coveralls, Inc