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

processone / ejabberd / 747

27 Jun 2024 01:43PM UTC coverage: 32.123% (+0.8%) from 31.276%
747

push

github

badlop
Set version to 24.06

14119 of 43953 relevant lines covered (32.12%)

614.73 hits per line

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

4.61
/src/mod_muc_admin.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_muc_admin.erl
3
%%% Author  : Badlop <badlop@ono.com>
4
%%% Purpose : Tools for additional MUC administration
5
%%% Created : 8 Sep 2007 by Badlop <badlop@ono.com>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2024   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@ono.com').
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_unregister_nick/2,
34
         create_room_with_opts/4, create_room/3, destroy_room/2,
35
         create_rooms_file/1, destroy_rooms_file/1,
36
         rooms_unused_list/2, rooms_unused_destroy/2,
37
         rooms_empty_list/1, rooms_empty_destroy/1, rooms_empty_destroy_restuple/1,
38
         get_user_rooms/2, get_user_subscriptions/2, get_room_occupants/2,
39
         get_room_occupants_number/2, send_direct_invitation/5,
40
         change_room_option/4, get_room_options/2,
41
         set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
42
         subscribe_room/4, subscribe_room_many/3,
43
         unsubscribe_room/2, get_subscribers/2,
44
         get_room_serverhost/1,
45
         web_menu_main/2, web_page_main/2,
46
         web_menu_host/3, web_page_host/3,
47
         web_menu_hostuser/4, web_page_hostuser/4,
48
         webadmin_muc/2,
49
         mod_opt_type/1, mod_options/1,
50
         get_commands_spec/0, find_hosts/1, room_diagnostics/2,
51
         get_room_pid/2, get_room_history/2]).
52

53
-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/4]).
54

55
-include("logger.hrl").
56
-include_lib("xmpp/include/xmpp.hrl").
57
-include("mod_muc.hrl").
58
-include("mod_muc_room.hrl").
59
-include("ejabberd_http.hrl").
60
-include("ejabberd_web_admin.hrl").
61
-include("ejabberd_commands.hrl").
62
-include("translate.hrl").
63

64
%%----------------------------
65
%% gen_mod
66
%%----------------------------
67

68
start(_Host, _Opts) ->
69
    ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
3✔
70
    {ok, [{hook, webadmin_menu_main, web_menu_main, 50, global},
3✔
71
          {hook, webadmin_page_main, web_page_main, 50, global},
72
          {hook, webadmin_menu_host, web_menu_host, 50},
73
          {hook, webadmin_page_host, web_page_host, 50},
74
          {hook, webadmin_menu_hostuser, web_menu_hostuser, 50},
75
          {hook, webadmin_page_hostuser, web_page_hostuser, 50}
76
         ]}.
77

78
stop(Host) ->
79
    case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
3✔
80
        false ->
81
            ejabberd_commands:unregister_commands(get_commands_spec());
1✔
82
        true ->
83
            ok
2✔
84
    end.
85

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

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

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

96
get_commands_spec() ->
97
    [
4✔
98
     #ejabberd_commands{name = muc_online_rooms, tags = [muc],
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",
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_unregister_nick, tags = [muc],
137
                       desc = "Unregister the nick registered by that account in the MUC service",
138
                       module = ?MODULE, function = muc_unregister_nick,
139
                       args_desc = ["User JID", "MUC service"],
140
                       args_example = [<<"tim@example.org">>, <<"conference.example.org">>],
141
                       args = [{jid, binary}, {service, binary}],
142
                       args_rename = [{host, service}],
143
                       result = {res, rescode}},
144

145
     #ejabberd_commands{name = create_room, tags = [muc_room],
146
                       desc = "Create a MUC room name@service in host",
147
                       module = ?MODULE, function = create_room,
148
                       args_desc = ["Room name", "MUC service", "Server host"],
149
                       args_example = ["room1", "conference.example.com", "example.com"],
150
                       args = [{name, binary}, {service, binary},
151
                               {host, binary}],
152
                       result = {res, rescode}},
153
     #ejabberd_commands{name = destroy_room, tags = [muc_room],
154
                       desc = "Destroy a MUC room",
155
                       module = ?MODULE, function = destroy_room,
156
                       args_desc = ["Room name", "MUC service"],
157
                       args_example = ["room1", "conference.example.com"],
158
                       args = [{name, binary}, {service, binary}],
159
                       result = {res, rescode}},
160
     #ejabberd_commands{name = create_rooms_file, tags = [muc],
161
                       desc = "Create the rooms indicated in file",
162
                       longdesc = "Provide one room JID per line. Rooms will be created after restart.",
163
                       module = ?MODULE, function = create_rooms_file,
164
                       args_desc = ["Path to the text file with one room JID per line"],
165
                       args_example = ["/home/ejabberd/rooms.txt"],
166
                       args = [{file, string}],
167
                       result = {res, rescode}},
168
     #ejabberd_commands{name = create_room_with_opts, tags = [muc_room, muc_sub],
169
                       desc = "Create a MUC room name@service in host with given options",
170
                       longdesc =
171
                        "The syntax of `affiliations` is: `Type:JID,Type:JID`. "
172
                        "The syntax of `subscribers` is: `JID:Nick:Node:Node2:Node3,JID:Nick:Node`.",
173
                       module = ?MODULE, function = create_room_with_opts,
174
                       args_desc = ["Room name", "MUC service", "Server host", "List of options"],
175
                       args_example = ["room1", "conference.example.com", "localhost",
176
                                       [{"members_only","true"},
177
                                        {"affiliations", "owner:bob@example.com,member:peter@example.com"},
178
                                        {"subscribers", "bob@example.com:Bob:messages:subject,anne@example.com:Anne:messages"}]],
179
                       args = [{name, binary}, {service, binary},
180
                               {host, binary},
181
                               {options, {list,
182
                                          {option, {tuple,
183
                                                    [{name, binary},
184
                                                     {value, binary}
185
                                                    ]}}
186
                                         }}],
187
                       result = {res, rescode}},
188
     #ejabberd_commands{name = destroy_rooms_file, tags = [muc],
189
                       desc = "Destroy the rooms indicated in file",
190
                       longdesc = "Provide one room JID per line.",
191
                       module = ?MODULE, function = destroy_rooms_file,
192
                       args_desc = ["Path to the text file with one room JID per line"],
193
                       args_example = ["/home/ejabberd/rooms.txt"],
194
                       args = [{file, string}],
195
                       result = {res, rescode}},
196
     #ejabberd_commands{name = rooms_unused_list, tags = [muc],
197
                       desc = "List the rooms that are unused for many days in the service",
198
                       longdesc = "The room recent history is used, so it's recommended "
199
                            " to wait a few days after service start before running this."
200
                            " The MUC service argument can be `global` to get all hosts.",
201
                       module = ?MODULE, function = rooms_unused_list,
202
                       args_desc = ["MUC service, or `global` for all", "Number of days"],
203
                       args_example = ["conference.example.com", 31],
204
                       result_desc = "List of unused rooms",
205
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
206
                       args = [{service, binary}, {days, integer}],
207
                       args_rename = [{host, service}],
208
                       result = {rooms, {list, {room, string}}}},
209
     #ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
210
                       desc = "Destroy the rooms that are unused for many days in the service",
211
                       longdesc = "The room recent history is used, so it's recommended "
212
                            " to wait a few days after service start before running this."
213
                            " The MUC service argument can be `global` to get all hosts.",
214
                       module = ?MODULE, function = rooms_unused_destroy,
215
                       args_desc = ["MUC service, or `global` for all", "Number of days"],
216
                       args_example = ["conference.example.com", 31],
217
                       result_desc = "List of unused rooms that has been destroyed",
218
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
219
                       args = [{service, binary}, {days, integer}],
220
                       args_rename = [{host, service}],
221
                       result = {rooms, {list, {room, string}}}},
222

223
     #ejabberd_commands{name = rooms_empty_list, tags = [muc],
224
                       desc = "List the rooms that have no messages in archive",
225
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
226
                       module = ?MODULE, function = rooms_empty_list,
227
                       args_desc = ["MUC service, or `global` for all"],
228
                       args_example = ["conference.example.com"],
229
                       result_desc = "List of empty rooms",
230
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
231
                       args = [{service, binary}],
232
                       args_rename = [{host, service}],
233
                       result = {rooms, {list, {room, string}}}},
234
     #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
235
                       desc = "Destroy the rooms that have no messages in archive",
236
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
237
                       module = ?MODULE, function = rooms_empty_destroy,
238
                       args_desc = ["MUC service, or `global` for all"],
239
                       args_example = ["conference.example.com"],
240
                       result_desc = "List of empty rooms that have been destroyed",
241
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
242
                       args = [{service, binary}],
243
                       args_rename = [{host, service}],
244
                       result = {rooms, {list, {room, string}}}},
245
     #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
246
                       desc = "Destroy the rooms that have no messages in archive",
247
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
248
                       module = ?MODULE, function = rooms_empty_destroy_restuple,
249
                       version = 2,
250
                       note = "modified in 24.06",
251
                       args_desc = ["MUC service, or `global` for all"],
252
                       args_example = ["conference.example.com"],
253
                       result_desc = "List of empty rooms that have been destroyed",
254
                       result_example = {ok, <<"Destroyed rooms: 2">>},
255
                       args = [{service, binary}],
256
                       args_rename = [{host, service}],
257
                       result = {res, restuple}},
258

259
     #ejabberd_commands{name = get_user_rooms, tags = [muc],
260
                        desc = "Get the list of rooms where this user is occupant",
261
                        module = ?MODULE, function = get_user_rooms,
262
                        args_desc = ["Username", "Server host"],
263
                        args_example = ["tom", "example.com"],
264
                        result_example = ["room1@conference.example.com", "room2@conference.example.com"],
265
                        args = [{user, binary}, {host, binary}],
266
                        result = {rooms, {list, {room, string}}}},
267
     #ejabberd_commands{name = get_user_subscriptions, tags = [muc, muc_sub],
268
                        desc = "Get the list of rooms where this user is subscribed",
269
                        note = "added in 21.04",
270
                        module = ?MODULE, function = get_user_subscriptions,
271
                        args_desc = ["Username", "Server host"],
272
                        args_example = ["tom", "example.com"],
273
                        result_example = [{"room1@conference.example.com", "Tommy", ["mucsub:config"]}],
274
                        args = [{user, binary}, {host, binary}],
275
                        result = {rooms,
276
                                  {list,
277
                                   {room,
278
                                    {tuple,
279
                                     [{roomjid, string},
280
                                      {usernick, string},
281
                                      {nodes, {list, {node, string}}}
282
                                     ]}}
283
                                  }}},
284

285
     #ejabberd_commands{name = get_room_occupants, tags = [muc_room],
286
                        desc = "Get the list of occupants of a MUC room",
287
                        module = ?MODULE, function = get_room_occupants,
288
                        args_desc = ["Room name", "MUC service"],
289
                        args_example = ["room1", "conference.example.com"],
290
                        result_desc = "The list of occupants with JID, nick and affiliation",
291
                        result_example = [{"user1@example.com/psi", "User 1", "owner"}],
292
                        args = [{name, binary}, {service, binary}],
293
                        result = {occupants, {list,
294
                                              {occupant, {tuple,
295
                                                          [{jid, string},
296
                                                           {nick, string},
297
                                                           {role, string}
298
                                                          ]}}
299
                                             }}},
300

301
     #ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
302
                        desc = "Get the number of occupants of a MUC room",
303
                        module = ?MODULE, function = get_room_occupants_number,
304
                        args_desc = ["Room name", "MUC service"],
305
                        args_example = ["room1", "conference.example.com"],
306
                        result_desc = "Number of room occupants",
307
                        result_example = 7,
308
                        args = [{name, binary}, {service, binary}],
309
                        result = {occupants, integer}},
310

311
     #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
312
                        desc = "Send a direct invitation to several destinations",
313
                        longdesc = "Since ejabberd 20.12, this command is "
314
                        "asynchronous: the API call may return before the "
315
                        "server has send all the invitations.\n\n"
316
                        "Password and Message can also be: `none`. "
317
                        "Users JIDs are separated with `:`.",
318
                        module = ?MODULE, function = send_direct_invitation,
319
                        args_desc = ["Room name", "MUC service", "Password, or `none`",
320
                         "Reason text, or `none`", "Users JIDs separated with `:` characters"],
321
                        args_example = [<<"room1">>, <<"conference.example.com">>,
322
                                        <<>>, <<"Check this out!">>,
323
                                        "user2@localhost:user3@example.com"],
324
                        args = [{name, binary}, {service, binary}, {password, binary},
325
                                {reason, binary}, {users, binary}],
326
                        result = {res, rescode}},
327
     #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
328
                        desc = "Send a direct invitation to several destinations",
329
                        longdesc = "Since ejabberd 20.12, this command is "
330
                        "asynchronous: the API call may return before the "
331
                        "server has send all the invitations.\n\n"
332
                        "`password` and `message` can be set to `none`.",
333
                        module = ?MODULE, function = send_direct_invitation,
334
                        version = 1,
335
                        note = "updated in 24.02",
336
                        args_desc = ["Room name", "MUC service", "Password, or `none`",
337
                         "Reason text, or `none`", "List of users JIDs"],
338
                        args_example = [<<"room1">>, <<"conference.example.com">>,
339
                                        <<>>, <<"Check this out!">>,
340
                                        ["user2@localhost", "user3@example.com"]],
341
                        args = [{name, binary}, {service, binary}, {password, binary},
342
                                {reason, binary}, {users, {list, {jid, binary}}}],
343
                        result = {res, rescode}},
344

345
     #ejabberd_commands{name = change_room_option, tags = [muc_room],
346
                       desc = "Change an option in a MUC room",
347
                       module = ?MODULE, function = change_room_option,
348
                       args_desc = ["Room name", "MUC service", "Option name", "Value to assign"],
349
                       args_example = ["room1", "conference.example.com", "members_only", "true"],
350
                       args = [{name, binary}, {service, binary},
351
                               {option, binary}, {value, binary}],
352
                       result = {res, rescode}},
353
     #ejabberd_commands{name = get_room_options, tags = [muc_room],
354
                        desc = "Get options from a MUC room",
355
                        module = ?MODULE, function = get_room_options,
356
                        args_desc = ["Room name", "MUC service"],
357
                        args_example = ["room1", "conference.example.com"],
358
                        result_desc = "List of room options tuples with name and value",
359
                        result_example = [{"members_only", "true"}],
360
                        args = [{name, binary}, {service, binary}],
361
                        result = {options, {list,
362
                                                 {option, {tuple,
363
                                                                [{name, string},
364
                                                                 {value, string}
365
                                                                ]}}
366
                                                }}},
367
     #ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
368
                        desc = "Subscribe to a MUC conference",
369
                        module = ?MODULE, function = subscribe_room,
370
                        args_desc = ["User JID", "a user's nick",
371
                            "the room to subscribe", "nodes separated by commas: `,`"],
372
                        args_example = ["tom@localhost", "Tom", "room1@conference.localhost",
373
                            "urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
374
                        result_desc = "The list of nodes that has subscribed",
375
                        result_example = ["urn:xmpp:mucsub:nodes:messages",
376
                            "urn:xmpp:mucsub:nodes:affiliations"],
377
                        args = [{user, binary}, {nick, binary}, {room, binary},
378
                                {nodes, binary}],
379
                        result = {nodes, {list, {node, string}}}},
380
     #ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
381
                        desc = "Subscribe to a MUC conference",
382
                        module = ?MODULE, function = subscribe_room,
383
                        version = 1,
384
                        note = "updated in 24.02",
385
                        args_desc = ["User JID", "a user's nick",
386
                            "the room to subscribe", "list of nodes"],
387
                        args_example = ["tom@localhost", "Tom", "room1@conference.localhost",
388
                            ["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
389
                        result_desc = "The list of nodes that has subscribed",
390
                        result_example = ["urn:xmpp:mucsub:nodes:messages",
391
                            "urn:xmpp:mucsub:nodes:affiliations"],
392
                        args = [{user, binary}, {nick, binary}, {room, binary},
393
                                {nodes, {list, {node, binary}}}],
394
                        result = {nodes, {list, {node, string}}}},
395
     #ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
396
                        desc = "Subscribe several users to a MUC conference",
397
                        note = "added in 22.05",
398
                        longdesc = "This command accepts up to 50 users at once "
399
                            "(this is configurable with the _`mod_muc_admin`_ option "
400
                            "`subscribe_room_many_max_users`)",
401
                        module = ?MODULE, function = subscribe_room_many,
402
                        args_desc = ["Users JIDs and nicks",
403
                                     "the room to subscribe",
404
                                     "nodes separated by commas: `,`"],
405
                        args_example = [[{"tom@localhost", "Tom"},
406
                                         {"jerry@localhost", "Jerry"}],
407
                                        "room1@conference.localhost",
408
                                        "urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
409
                        args = [{users, {list,
410
                                         {user, {tuple,
411
                                                 [{jid, binary},
412
                                                  {nick, binary}
413
                                                 ]}}
414
                                        }},
415
                                {room, binary},
416
                                {nodes, binary}],
417
                        result = {res, rescode}},
418
     #ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
419
                        desc = "Subscribe several users to a MUC conference",
420
                        longdesc = "This command accepts up to 50 users at once "
421
                            "(this is configurable with the _`mod_muc_admin`_ option "
422
                            "`subscribe_room_many_max_users`)",
423
                        module = ?MODULE, function = subscribe_room_many,
424
                        version = 1,
425
                        note = "updated in 24.02",
426
                        args_desc = ["Users JIDs and nicks",
427
                                     "the room to subscribe",
428
                                     "nodes separated by commas: `,`"],
429
                        args_example = [[{"tom@localhost", "Tom"},
430
                                         {"jerry@localhost", "Jerry"}],
431
                                        "room1@conference.localhost",
432
                                        ["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
433
                        args = [{users, {list,
434
                                         {user, {tuple,
435
                                                 [{jid, binary},
436
                                                  {nick, binary}
437
                                                 ]}}
438
                                        }},
439
                                {room, binary},
440
                                {nodes, {list, {node, binary}}}],
441
                        result = {res, rescode}},
442
     #ejabberd_commands{name = unsubscribe_room, tags = [muc_room, muc_sub],
443
                        desc = "Unsubscribe from a MUC conference",
444
                        module = ?MODULE, function = unsubscribe_room,
445
                        args_desc = ["User JID", "the room to subscribe"],
446
                        args_example = ["tom@localhost", "room1@conference.localhost"],
447
                        args = [{user, binary}, {room, binary}],
448
                        result = {res, rescode}},
449
     #ejabberd_commands{name = get_subscribers, tags = [muc_room, muc_sub],
450
                        desc = "List subscribers of a MUC conference",
451
                        module = ?MODULE, function = get_subscribers,
452
                        args_desc = ["Room name", "MUC service"],
453
                        args_example = ["room1", "conference.example.com"],
454
                        result_desc = "The list of users that are subscribed to that room",
455
                        result_example = ["user2@example.com", "user3@example.com"],
456
                        args = [{name, binary}, {service, binary}],
457
                        result = {subscribers, {list, {jid, string}}}},
458
     #ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
459
                       desc = "Change an affiliation in a MUC room",
460
                       module = ?MODULE, function = set_room_affiliation,
461
                       args_desc = ["Room name", "MUC service", "User JID", "Affiliation to set"],
462
                       args_example = ["room1", "conference.example.com", "user2@example.com", "member"],
463
                       args = [{name, binary}, {service, binary},
464
                               {jid, binary}, {affiliation, binary}],
465
                       result = {res, rescode}},
466
     #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
467
                        desc = "Get the list of affiliations of a MUC room",
468
                        module = ?MODULE, function = get_room_affiliations,
469
                        args_desc = ["Room name", "MUC service"],
470
                        args_example = ["room1", "conference.example.com"],
471
                        result_desc = "The list of affiliations with username, domain, affiliation and reason",
472
                        result_example = [{"user1", "example.com", member, "member"}],
473
                        args = [{name, binary}, {service, binary}],
474
                        result = {affiliations, {list,
475
                                                 {affiliation, {tuple,
476
                                                                [{username, string},
477
                                                                 {domain, string},
478
                                                                 {affiliation, atom},
479
                                                                 {reason, string}
480
                                                                ]}}
481
                                                }}},
482
         #ejabberd_commands{name = get_room_affiliation, tags = [muc_room],
483
                        desc = "Get affiliation of a user in MUC room",
484
                        module = ?MODULE, function = get_room_affiliation,
485
                        args_desc = ["Room name", "MUC service", "User JID"],
486
                        args_example = ["room1", "conference.example.com", "user1@example.com"],
487
                        result_desc = "Affiliation of the user",
488
                        result_example = member,
489
                        args = [{name, binary}, {service, binary}, {jid, binary}],
490
                        result = {affiliation, atom}},
491
         #ejabberd_commands{name = get_room_history, tags = [muc_room],
492
                        desc = "Get history of messages stored inside MUC room state",
493
                        note = "added in 23.04",
494
                        module = ?MODULE, function = get_room_history,
495
                        args_desc = ["Room name", "MUC service"],
496
                        args_example = ["room1", "conference.example.com"],
497
                        args = [{name, binary}, {service, binary}],
498
                        result = {history, {list,
499
                                            {entry, {tuple,
500
                                                     [{timestamp, string},
501
                                                      {message, string}]}}}}},
502

503
         #ejabberd_commands{name = webadmin_muc, tags = [internal],
504
                        desc = "Generate WebAdmin MUC Rooms HTML",
505
                        module = ?MODULE, function = webadmin_muc,
506
                        args = [{request, any}, {lang, binary}],
507
                        result = {res, any}}
508
        ].
509

510

511
%%%
512
%%% ejabberd commands
513
%%%
514

515
muc_online_rooms(ServiceArg) ->
516
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
517
    lists:flatmap(
×
518
      fun(Host) ->
519
              [<<Name/binary, "@", Host/binary>>
×
520
               || {Name, _, _} <- mod_muc:get_online_rooms(Host)]
×
521
      end, Hosts).
522

523
muc_online_rooms_by_regex(ServiceArg, Regex) ->
524
    {_, P} = re:compile(Regex),
×
525
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
526
    lists:flatmap(
×
527
      fun(Host) ->
528
              [build_summary_room(Name, RoomHost, Pid)
×
529
               || {Name, RoomHost, Pid} <- mod_muc:get_online_rooms(Host),
×
530
                   is_name_match(Name, P)]
×
531
      end, Hosts).
532

533
is_name_match(Name, P) ->
534
        case re:run(Name, P) of
×
535
                {match, _} -> true;
×
536
                nomatch -> false
×
537
        end.
538

539
build_summary_room(Name, Host, Pid) ->
540
    C = get_room_config(Pid),
×
541
    Public = C#config.public,
×
542
    S = get_room_state(Pid),
×
543
    Participants = maps:size(S#state.users),
×
544
    {<<Name/binary, "@", Host/binary>>,
×
545
         misc:atom_to_binary(Public),
546
     Participants
547
    }.
548

549
muc_register_nick(Nick, FromBinary, Service) ->
550
    try {get_room_serverhost(Service), jid:decode(FromBinary)} of
×
551
        {ServerHost, From} ->
552
            Lang = <<"en">>,
×
553
            case mod_muc:iq_set_register_info(ServerHost, Service, From, Nick, Lang) of
×
554
                {result, undefined} -> ok;
×
555
                {error, #stanza_error{reason = 'conflict'}} ->
556
                    throw({error, "Nick already registered"});
×
557
                {error, _} ->
558
                    throw({error, "Database error"})
×
559
            end
560
        catch
561
        error:{invalid_domain, _} ->
562
            throw({error, "Invalid value of 'service'"});
×
563
        error:{unregistered_route, _} ->
564
            throw({error, "Unknown host in 'service'"});
×
565
        error:{bad_jid, _} ->
566
            throw({error, "Invalid 'jid'"});
×
567
        _ ->
568
            throw({error, "Internal error"})
×
569
    end.
570

571
muc_unregister_nick(FromBinary, Service) ->
572
    muc_register_nick(<<"">>, FromBinary, Service).
×
573

574
get_user_rooms(User, Server) ->
575
    lists:flatmap(
×
576
      fun(ServerHost) ->
577
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
578
                  true ->
579
                      Rooms = mod_muc:get_online_rooms_by_user(
×
580
                                ServerHost, jid:nodeprep(User), jid:nodeprep(Server)),
581
                      [<<Name/binary, "@", Host/binary>>
×
582
                           || {Name, Host} <- Rooms];
×
583
                  false ->
584
                      []
×
585
              end
586
      end, ejabberd_option:hosts()).
587

588
get_user_subscriptions(User, Server) ->
589
    User2 = validate_user(User, <<"user">>),
×
590
    Server2 = validate_host(Server, <<"host">>),
×
591
    Services = find_services(global),
×
592
    UserJid = jid:make(User2, Server2),
×
593
    lists:flatmap(
×
594
      fun(ServerHost) ->
595
              {ok, Rooms} = mod_muc:get_subscribed_rooms(ServerHost, UserJid),
×
596
              [{jid:encode(RoomJid), UserNick, Nodes}
×
597
               || {RoomJid, UserNick, Nodes} <- Rooms]
×
598
      end, Services).
599

600
%%----------------------------
601
%% Ad-hoc commands
602
%%----------------------------
603

604

605
%%----------------------------
606
%% Web Admin
607
%%----------------------------
608

609
%% @format-begin
610

611
%%---------------
612
%% Web Admin Menu
613

614
web_menu_main(Acc, Lang) ->
615
    Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}].
30✔
616

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

620
%%---------------
621
%% Web Admin Page
622

623
-define(TDTD(L, N),
624
        ?XE(<<"tr">>, [?XCT(<<"td">>, L), ?XC(<<"td">>, integer_to_binary(N))])).
625

626
web_page_main(_, #request{path = [<<"muc">>], lang = Lang} = R) ->
627
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
628
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
629
    Res = [make_command(webadmin_muc, R, [{<<"request">>, R}, {<<"lang">>, Lang}], [])],
×
630
    {stop, Title ++ Res};
×
631
web_page_main(Acc, _) ->
632
    Acc.
×
633

634
web_page_host(_, Host, #request{path = [<<"muc">> | RPath], lang = Lang} = R) ->
635
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
636
    Service = find_service(Host),
×
637
    Level = length(RPath),
×
638
    Res = webadmin_muc_host(Host, Service, RPath, R, Lang, Level, PageTitle),
×
639
    {stop, Res};
×
640
web_page_host(Acc, _, _) ->
641
    Acc.
×
642

643
%%---------------
644
%% WebAdmin MUC Host Page
645

646
webadmin_muc_host(Host,
647
                  Service,
648
                  [<<"create-room">> | RPath],
649
                  R,
650
                  _Lang,
651
                  Level,
652
                  PageTitle) ->
653
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
654
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Create Room">>, RPath}),
×
655
    Set = [make_command(create_room, R, [{<<"service">>, Service}, {<<"host">>, Host}], []),
×
656
           make_command(create_room_with_opts,
657
                        R,
658
                        [{<<"service">>, Service}, {<<"host">>, Host}],
659
                        [])],
660
    Title ++ Breadcrumb ++ Set;
×
661
webadmin_muc_host(_Host,
662
                  Service,
663
                  [<<"nick-register">> | RPath],
664
                  R,
665
                  _Lang,
666
                  Level,
667
                  PageTitle) ->
668
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
669
    Breadcrumb =
×
670
        make_breadcrumb({service_section, Level, Service, <<"Nick Register">>, RPath}),
671
    Set = [make_command(muc_register_nick, R, [{<<"service">>, Service}], []),
×
672
           make_command(muc_unregister_nick, R, [{<<"service">>, Service}], [])],
673
    Title ++ Breadcrumb ++ Set;
×
674
webadmin_muc_host(_Host,
675
                  Service,
676
                  [<<"rooms-empty">> | RPath],
677
                  R,
678
                  _Lang,
679
                  Level,
680
                  PageTitle) ->
681
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
682
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms Empty">>, RPath}),
×
683
    Set = [make_command(rooms_empty_list,
×
684
                        R,
685
                        [{<<"service">>, Service}],
686
                        [{table_options, {2, RPath}},
687
                         {result_links, [{room, room, 3 + Level, <<"">>}]}]),
688
           make_command(rooms_empty_destroy, R, [{<<"service">>, Service}], [])],
689
    Title ++ Breadcrumb ++ Set;
×
690
webadmin_muc_host(_Host,
691
                  Service,
692
                  [<<"rooms-unused">> | RPath],
693
                  R,
694
                  _Lang,
695
                  Level,
696
                  PageTitle) ->
697
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
698
    Breadcrumb =
×
699
        make_breadcrumb({service_section, Level, Service, <<"Rooms Unused">>, RPath}),
700
    Set = [make_command(rooms_unused_list,
×
701
                        R,
702
                        [{<<"service">>, Service}],
703
                        [{result_links, [{room, room, 3 + Level, <<"">>}]}]),
704
           make_command(rooms_unused_destroy, R, [{<<"service">>, Service}], [])],
705
    Title ++ Breadcrumb ++ Set;
×
706
webadmin_muc_host(_Host,
707
                  Service,
708
                  [<<"rooms-regex">> | RPath],
709
                  R,
710
                  _Lang,
711
                  Level,
712
                  PageTitle) ->
713
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
714
    Breadcrumb =
×
715
        make_breadcrumb({service_section, Level, Service, <<"Rooms by Regex">>, RPath}),
716
    Set = [make_command(muc_online_rooms_by_regex,
×
717
                        R,
718
                        [{<<"service">>, Service}],
719
                        [{result_links, [{jid, room, 3 + Level, <<"">>}]}])],
720
    Title ++ Breadcrumb ++ Set;
×
721
webadmin_muc_host(_Host,
722
                  Service,
723
                  [<<"rooms">>, <<"room">>, Name, <<"affiliations">> | RPath],
724
                  R,
725
                  _Lang,
726
                  Level,
727
                  PageTitle) ->
728
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
729
    Breadcrumb =
×
730
        make_breadcrumb({room_section, Level, Service, <<"Affiliations">>, Name, R, RPath}),
731
    Set = [make_command(set_room_affiliation,
×
732
                        R,
733
                        [{<<"name">>, Name}, {<<"service">>, Service}],
734
                        [])],
735
    Get = [make_command(get_room_affiliations,
×
736
                        R,
737
                        [{<<"name">>, Name}, {<<"service">>, Service}],
738
                        [{table_options, {20, RPath}}])],
739
    Title ++ Breadcrumb ++ Get ++ Set;
×
740
webadmin_muc_host(_Host,
741
                  Service,
742
                  [<<"rooms">>, <<"room">>, Name, <<"history">> | RPath],
743
                  R,
744
                  _Lang,
745
                  Level,
746
                  PageTitle) ->
747
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
748
    Breadcrumb =
×
749
        make_breadcrumb({room_section, Level, Service, <<"History">>, Name, R, RPath}),
750
    Get = [make_command(get_room_history,
×
751
                        R,
752
                        [{<<"name">>, Name}, {<<"service">>, Service}],
753
                        [{table_options, {10, RPath}},
754
                         {result_links, [{message, paragraph, 1, <<"">>}]}])],
755
    Title ++ Breadcrumb ++ Get;
×
756
webadmin_muc_host(_Host,
757
                  Service,
758
                  [<<"rooms">>, <<"room">>, Name, <<"invite">> | RPath],
759
                  R,
760
                  _Lang,
761
                  Level,
762
                  PageTitle) ->
763
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
764
    Breadcrumb =
×
765
        make_breadcrumb({room_section, Level, Service, <<"Invite">>, Name, R, RPath}),
766
    Set = [make_command(send_direct_invitation,
×
767
                        R,
768
                        [{<<"name">>, Name}, {<<"service">>, Service}],
769
                        [])],
770
    Title ++ Breadcrumb ++ Set;
×
771
webadmin_muc_host(_Host,
772
                  Service,
773
                  [<<"rooms">>, <<"room">>, Name, <<"occupants">> | RPath],
774
                  R,
775
                  _Lang,
776
                  Level,
777
                  PageTitle) ->
778
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
779
    Breadcrumb =
×
780
        make_breadcrumb({room_section, Level, Service, <<"Occupants">>, Name, R, RPath}),
781
    Get = [make_command(get_room_occupants,
×
782
                        R,
783
                        [{<<"name">>, Name}, {<<"service">>, Service}],
784
                        [{table_options, {20, RPath}},
785
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
786
    Title ++ Breadcrumb ++ Get;
×
787
webadmin_muc_host(_Host,
788
                  Service,
789
                  [<<"rooms">>, <<"room">>, Name, <<"options">> | RPath],
790
                  R,
791
                  _Lang,
792
                  Level,
793
                  PageTitle) ->
794
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
795
    Breadcrumb =
×
796
        make_breadcrumb({room_section, Level, Service, <<"Options">>, Name, R, RPath}),
797
    Set = [make_command(change_room_option,
×
798
                        R,
799
                        [{<<"name">>, Name}, {<<"service">>, Service}],
800
                        [])],
801
    Get = [make_command(get_room_options,
×
802
                        R,
803
                        [{<<"name">>, Name}, {<<"service">>, Service}],
804
                        [])],
805
    Title ++ Breadcrumb ++ Get ++ Set;
×
806
webadmin_muc_host(_Host,
807
                  Service,
808
                  [<<"rooms">>, <<"room">>, Name, <<"subscribers">> | RPath],
809
                  R,
810
                  _Lang,
811
                  Level,
812
                  PageTitle) ->
813
    Title =
×
814
        ?H1GLraw(PageTitle,
815
                 <<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
816
                 <<"MUC/Sub Extension">>),
817
    Breadcrumb =
×
818
        make_breadcrumb({room_section, Level, Service, <<"Subscribers">>, Name, R, RPath}),
819
    Set = [make_command(subscribe_room,
×
820
                        R,
821
                        [{<<"room">>, jid:encode({Name, Service, <<"">>})}],
822
                        []),
823
           make_command(unsubscribe_room,
824
                        R,
825
                        [{<<"room">>, jid:encode({Name, Service, <<"">>})}],
826
                        [{style, danger}])],
827
    Get = [make_command(get_subscribers,
×
828
                        R,
829
                        [{<<"name">>, Name}, {<<"service">>, Service}],
830
                        [{table_options, {20, RPath}},
831
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
832
    Title ++ Breadcrumb ++ Get ++ Set;
×
833
webadmin_muc_host(_Host,
834
                  Service,
835
                  [<<"rooms">>, <<"room">>, Name, <<"destroy">> | RPath],
836
                  R,
837
                  _Lang,
838
                  Level,
839
                  PageTitle) ->
840
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
841
    Breadcrumb =
×
842
        make_breadcrumb({room_section, Level, Service, <<"Destroy">>, Name, R, RPath}),
843
    Set = [make_command(destroy_room,
×
844
                        R,
845
                        [{<<"name">>, Name}, {<<"service">>, Service}],
846
                        [{style, danger}])],
847
    Title ++ Breadcrumb ++ Set;
×
848
webadmin_muc_host(_Host,
849
                  Service,
850
                  [<<"rooms">>, <<"room">>, Name | _RPath],
851
                  _R,
852
                  Lang,
853
                  Level,
854
                  PageTitle) ->
855
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
856
    Breadcrumb = make_breadcrumb({room, Level, Service, Name}),
×
857
    MenuItems =
×
858
        [{<<"affiliations/">>, <<"Affiliations">>},
859
         {<<"history/">>, <<"History">>},
860
         {<<"invite/">>, <<"Invite">>},
861
         {<<"occupants/">>, <<"Occupants">>},
862
         {<<"options/">>, <<"Options">>},
863
         {<<"subscribers/">>, <<"Subscribers">>},
864
         {<<"destroy/">>, <<"Destroy">>}],
865
    Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
×
866
    Title ++ Breadcrumb ++ Get;
×
867
webadmin_muc_host(_Host, Service, [<<"rooms">> | RPath], R, _Lang, Level, PageTitle) ->
868
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
869
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms">>, RPath}),
×
870
    Columns = [<<"jid">>, <<"occupants">>],
×
871
    Rows =
×
872
        lists:map(fun(NameService) ->
873
                     #jid{user = Name} = jid:decode(NameService),
×
874
                     {make_command(echo,
×
875
                                   R,
876
                                   [{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
877
                                   [{only, raw_and_value},
878
                                    {result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
879
                      make_command(get_room_occupants_number,
880
                                   R,
881
                                   [{<<"name">>, Name}, {<<"service">>, Service}],
882
                                   [{only, raw_and_value}])}
883
                  end,
884
                  make_command_raw_value(muc_online_rooms, R, [{<<"service">>, Service}])),
885
    Get = [make_command(muc_online_rooms, R, [], [{only, presentation}]),
×
886
           make_command(get_room_occupants_number, R, [], [{only, presentation}]),
887
           make_table(20, RPath, Columns, Rows)],
888
    Title ++ Breadcrumb ++ Get;
×
889
webadmin_muc_host(_Host, Service, [], _R, Lang, _Level, PageTitle) ->
890
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
891
    Breadcrumb = make_breadcrumb({service, Service}),
×
892
    MenuItems =
×
893
        [{<<"create-room/">>, <<"Create Room">>},
894
         {<<"rooms/">>, <<"Rooms">>},
895
         {<<"rooms-regex/">>, <<"Rooms by Regex">>},
896
         {<<"rooms-empty/">>, <<"Rooms Empty">>},
897
         {<<"rooms-unused/">>, <<"Rooms Unused">>},
898
         {<<"nick-register/">>, <<"Nick Register">>}],
899
    Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
×
900
    Title ++ Breadcrumb ++ Get;
×
901
webadmin_muc_host(_Host, _Service, _RPath, _R, _Lang, _Level, _PageTitle) ->
902
    [].
×
903

904
make_breadcrumb({service, Service}) ->
905
    make_breadcrumb([Service]);
×
906
make_breadcrumb({service_section, Level, Service, Section, RPath}) ->
907
    make_breadcrumb([{Level, Service}, separator, Section | RPath]);
×
908
make_breadcrumb({room, Level, Service, Name}) ->
909
    make_breadcrumb([{Level, Service},
×
910
                     separator,
911
                     {Level - 1, <<"Rooms">>},
912
                     separator,
913
                     jid:encode({Name, Service, <<"">>})]);
914
make_breadcrumb({room_section, Level, Service, Section, Name, R, RPath}) ->
915
    make_breadcrumb([{Level, Service},
×
916
                     separator,
917
                     {Level - 1, <<"Rooms">>},
918
                     separator,
919
                     make_command(echo,
920
                                  R,
921
                                  [{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
922
                                  [{only, value},
923
                                   {result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
924
                     separator,
925
                     Section
926
                     | RPath]);
927
make_breadcrumb(Elements) ->
928
    lists:map(fun ({xmlel, _, _, _} = Xmlel) ->
×
929
                      Xmlel;
×
930
                  (<<"sort">>) ->
931
                      ?C(<<" +">>);
×
932
                  (<<"page">>) ->
933
                      ?C(<<" #">>);
×
934
                  (separator) ->
935
                      ?C(<<" > ">>);
×
936
                  (Bin) when is_binary(Bin) ->
937
                      ?C(Bin);
×
938
                  ({Level, Bin}) when is_integer(Level) and is_binary(Bin) ->
939
                      ?AC(binary:copy(<<"../">>, Level), Bin)
×
940
              end,
941
              Elements).
942

943
%%---------------
944
%%
945

946
%% Returns: {normal | reverse, Integer}
947
get_sort_query(Q) ->
948
    case catch get_sort_query2(Q) of
×
949
        {ok, Res} ->
950
            Res;
×
951
        _ ->
952
            {normal, 1}
×
953
    end.
954

955
get_sort_query2(Q) ->
956
    {value, {_, Binary}} = lists:keysearch(<<"sort">>, 1, Q),
×
957
    Integer = list_to_integer(string:strip(binary_to_list(Binary), right, $/)),
×
958
    case Integer >= 0 of
×
959
        true ->
960
            {ok, {normal, Integer}};
×
961
        false ->
962
            {ok, {reverse, abs(Integer)}}
×
963
    end.
964

965
webadmin_muc(#request{q = Q} = R, Lang) ->
966
    {Sort_direction, Sort_column} = get_sort_query(Q),
×
967
    Host = global,
×
968
    Service = find_service(Host),
×
969
    Rooms_names = get_online_rooms(Service),
×
970
    Rooms_infos = build_info_rooms(Rooms_names),
×
971
    Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
×
972
    Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
×
973
    TList =
×
974
        lists:map(fun([RoomJid | Room]) ->
975
                     JidLink =
×
976
                         make_command(echo,
977
                                      R,
978
                                      [{<<"sentence">>, RoomJid}],
979
                                      [{only, value},
980
                                       {result_links, [{sentence, room, 1, <<"">>}]}]),
981
                     ?XE(<<"tr">>, [?XE(<<"td">>, [JidLink]) | [?XC(<<"td">>, E) || E <- Room]])
×
982
                  end,
983
                  Rooms_prepared),
984
    Titles =
×
985
        [?T("Jabber ID"),
986
         ?T("# participants"),
987
         ?T("Last message"),
988
         ?T("Public"),
989
         ?T("Persistent"),
990
         ?T("Logging"),
991
         ?T("Just created"),
992
         ?T("Room title"),
993
         ?T("Node")],
994
    {Titles_TR, _} =
×
995
        lists:mapfoldl(fun(Title, Num_column) ->
996
                          NCS = integer_to_binary(Num_column),
×
997
                          TD = ?XE(<<"td">>,
×
998
                                   [?CT(Title),
999
                                    ?C(<<" ">>),
1000
                                    ?AC(<<"?sort=", NCS/binary>>, <<"<">>),
1001
                                    ?C(<<" ">>),
1002
                                    ?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
1003
                          {TD, Num_column + 1}
×
1004
                       end,
1005
                       1,
1006
                       Titles),
1007
    [?XCT(<<"h2">>, ?T("Chatrooms")),
×
1008
     ?XE(<<"table">>,
1009
         [?XE(<<"thead">>, [?XE(<<"tr">>, Titles_TR)]), ?XE(<<"tbody">>, TList)])].
1010

1011
sort_rooms(Direction, Column, Rooms) ->
1012
    Rooms2 = lists:keysort(Column, Rooms),
×
1013
    case Direction of
×
1014
        normal ->
1015
            Rooms2;
×
1016
        reverse ->
1017
            lists:reverse(Rooms2)
×
1018
    end.
1019

1020
build_info_rooms(Rooms) ->
1021
    [build_info_room(Room) || Room <- Rooms].
×
1022

1023
build_info_room({Name, Host, _ServerHost, Pid}) ->
1024
    C = get_room_config(Pid),
×
1025
    Title = C#config.title,
×
1026
    Public = C#config.public,
×
1027
    Persistent = C#config.persistent,
×
1028
    Logging = C#config.logging,
×
1029

1030
    S = get_room_state(Pid),
×
1031
    Just_created = S#state.just_created,
×
1032
    Num_participants = maps:size(S#state.users),
×
1033
    Node = node(Pid),
×
1034

1035
    History = S#state.history#lqueue.queue,
×
1036
    Ts_last_message =
×
1037
        case p1_queue:is_empty(History) of
1038
            true ->
1039
                <<"A long time ago">>;
×
1040
            false ->
1041
                Last_message1 = get_queue_last(History),
×
1042
                {_, _, _, Ts_last, _} = Last_message1,
×
1043
                xmpp_util:encode_timestamp(Ts_last)
×
1044
        end,
1045

1046
    {<<Name/binary, "@", Host/binary>>,
×
1047
     Num_participants,
1048
     Ts_last_message,
1049
     Public,
1050
     Persistent,
1051
     Logging,
1052
     Just_created,
1053
     Title,
1054
     Node}.
1055

1056
get_queue_last(Queue) ->
1057
    List = p1_queue:to_list(Queue),
×
1058
    lists:last(List).
×
1059

1060
prepare_rooms_infos(Rooms) ->
1061
    [prepare_room_info(Room) || Room <- Rooms].
×
1062

1063
prepare_room_info(Room_info) ->
1064
    {NameHost,
×
1065
     Num_participants,
1066
     Ts_last_message,
1067
     Public,
1068
     Persistent,
1069
     Logging,
1070
     Just_created,
1071
     Title,
1072
     Node} =
1073
        Room_info,
1074
    [NameHost,
×
1075
     integer_to_binary(Num_participants),
1076
     Ts_last_message,
1077
     misc:atom_to_binary(Public),
1078
     misc:atom_to_binary(Persistent),
1079
     misc:atom_to_binary(Logging),
1080
     justcreated_to_binary(Just_created),
1081
     Title,
1082
     misc:atom_to_binary(Node)].
1083

1084
justcreated_to_binary(J) when is_integer(J) ->
1085
    JNow = misc:usec_to_now(J),
×
1086
    {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(JNow),
×
1087
    str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
×
1088
               [Year, Month, Day, Hour, Minute, Second]);
1089
justcreated_to_binary(J) when is_atom(J) ->
1090
    misc:atom_to_binary(J).
×
1091

1092
%%--------------------
1093
%% Web Admin Host User
1094

1095
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
1096
    Acc
1097
    ++ [{<<"muc-rooms">>, <<"MUC Rooms Online">>},
×
1098
        {<<"muc-affiliations">>, <<"MUC Rooms Affiliations">>},
1099
        {<<"muc-sub">>, <<"MUC Rooms Subscriptions">>},
1100
        {<<"muc-register">>, <<"MUC Service Registration">>}].
1101

1102
web_page_hostuser(_, Host, User, #request{path = [<<"muc-rooms">> | RPath]} = R) ->
1103
    Level = 5 + length(RPath),
×
1104
    Res = ?H1GL(<<"MUC Rooms Online">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1105
          ++ [make_command(get_user_rooms,
1106
                           R,
1107
                           [{<<"user">>, User}, {<<"host">>, Host}],
1108
                           [{table_options, {2, RPath}},
1109
                            {result_links, [{room, room, Level, <<"">>}]}])],
1110
    {stop, Res};
×
1111
web_page_hostuser(_, Host, User, #request{path = [<<"muc-affiliations">>]} = R) ->
1112
    Jid = jid:encode(
×
1113
              jid:make(User, Host)),
1114
    Res = ?H1GL(<<"MUC Rooms Affiliations">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1115
          ++ [make_command(set_room_affiliation, R, [{<<"jid">>, Jid}], []),
1116
              make_command(get_room_affiliation, R, [{<<"jid">>, Jid}], [])],
1117
    {stop, Res};
×
1118
web_page_hostuser(_, Host, User, #request{path = [<<"muc-sub">> | RPath]} = R) ->
1119
    Title =
×
1120
        ?H1GLraw(<<"MUC Rooms Subscriptions">>,
1121
                 <<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
1122
                 <<"MUC/Sub">>),
1123
    Level = 5 + length(RPath),
×
1124
    Set = [make_command(subscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
×
1125
           make_command(unsubscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
1126
    Get = [make_command(get_user_subscriptions,
×
1127
                        R,
1128
                        [{<<"user">>, User}, {<<"host">>, Host}],
1129
                        [{table_options, {20, RPath}},
1130
                         {result_links, [{roomjid, room, Level, <<"">>}]}])],
1131
    {stop, Title ++ Get ++ Set};
×
1132
web_page_hostuser(_, Host, User, #request{path = [<<"muc-register">>]} = R) ->
1133
    Jid = jid:encode(
×
1134
              jid:make(User, Host)),
1135
    Res = ?H1GL(<<"MUC Service Registration">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1136
          ++ [make_command(muc_register_nick, R, [{<<"jid">>, Jid}], []),
1137
              make_command(muc_unregister_nick, R, [{<<"jid">>, Jid}], [])],
1138
    {stop, Res};
×
1139
web_page_hostuser(Acc, _, _, _) ->
1140
    Acc.
×
1141
%% @format-end
1142

1143
%%----------------------------
1144
%% Create/Delete Room
1145
%%----------------------------
1146

1147
-spec create_room(Name::binary(), Host::binary(), ServerHost::binary()) -> ok | error.
1148
%% @doc Create a room immediately with the default options.
1149
create_room(Name1, Host1, ServerHost) ->
1150
    create_room_with_opts(Name1, Host1, ServerHost, []).
×
1151

1152
create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
1153
    ServerHost = validate_host(ServerHost1, <<"serverhost">>),
×
1154
    case get_room_pid_validate(Name1, Host1, <<"name">>, <<"host">>) of
×
1155
        {room_not_found, Name, Host} ->
1156
            %% Get the default room options from the muc configuration
1157
            DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
×
1158
            %% Change default room options as required
1159
            FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
×
1160
            RoomOpts = lists:ukeymerge(1,
×
1161
                lists:keysort(1, FormattedRoomOpts),
1162
                lists:keysort(1, DefRoomOpts)),
1163
            case mod_muc:create_room(Host, Name, RoomOpts) of
×
1164
                ok ->
1165
                    ok;
×
1166
                {error, _} ->
1167
                    throw({error, "Unable to start room"})
×
1168
            end;
1169
        _ ->
1170
            throw({error, "Room already exists"})
×
1171
    end.
1172

1173
%% Create the room only in the database.
1174
%% It is required to restart the MUC service for the room to appear.
1175
muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
1176
    io:format("Creating room ~ts@~ts~n", [Name, Host]),
×
1177
    mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts).
×
1178

1179
-spec destroy_room(Name::binary(), Host::binary()) -> ok | {error, room_not_exists}.
1180
%% @doc Destroy the room immediately.
1181
%% If the room has participants, they are not notified that the room was destroyed;
1182
%% they will notice when they try to chat and receive an error that the room doesn't exist.
1183
destroy_room(Name1, Service1) ->
1184
    case get_room_pid_validate(Name1, Service1, <<"name">>, <<"service">>) of
×
1185
        {room_not_found, _, _} ->
1186
            throw({error, "Room doesn't exists"});
×
1187
        {Pid, _, _} ->
1188
            mod_muc_room:destroy(Pid),
×
1189
            ok
×
1190
    end.
1191

1192
destroy_room({N, H, SH}) ->
1193
    io:format("Destroying room: ~ts@~ts - vhost: ~ts~n", [N, H, SH]),
×
1194
    destroy_room(N, H).
×
1195

1196

1197
%%----------------------------
1198
%% Destroy Rooms in File
1199
%%----------------------------
1200

1201
%% The format of the file is: one chatroom JID per line
1202
%% The file encoding must be UTF-8
1203

1204
destroy_rooms_file(Filename) ->
1205
    {ok, F} = file:open(Filename, [read]),
×
1206
    RJID = read_room(F),
×
1207
    Rooms = read_rooms(F, RJID, []),
×
1208
    file:close(F),
×
1209
    [destroy_room(A) || A <- Rooms],
×
1210
    ok.
×
1211

1212
read_rooms(_F, eof, L) ->
1213
    L;
×
1214
read_rooms(F, no_room, L) ->
1215
    RJID2 = read_room(F),
×
1216
    read_rooms(F, RJID2, L);
×
1217
read_rooms(F, RJID, L) ->
1218
    RJID2 = read_room(F),
×
1219
    read_rooms(F, RJID2, [RJID | L]).
×
1220

1221
read_room(F) ->
1222
    case io:get_line(F, "") of
×
1223
        eof -> eof;
×
1224
        String ->
1225
            case io_lib:fread("~ts", String) of
×
1226
                {ok, [RoomJID], _} -> split_roomjid(list_to_binary(RoomJID));
×
1227
                {error, What} ->
1228
                    io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
×
1229
            end
1230
    end.
1231

1232
%% This function is quite rudimentary
1233
%% and may not be accurate
1234
split_roomjid(RoomJID) ->
1235
    split_roomjid2(binary:split(RoomJID, <<"@">>)).
×
1236
split_roomjid2([Name, Host]) ->
1237
    [_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
×
1238
    {Name, Host, ServerHost};
×
1239
split_roomjid2(_) ->
1240
    no_room.
×
1241

1242
%%----------------------------
1243
%% Create Rooms in File
1244
%%----------------------------
1245

1246
create_rooms_file(Filename) ->
1247
    {ok, F} = file:open(Filename, [read]),
×
1248
    RJID = read_room(F),
×
1249
    Rooms = read_rooms(F, RJID, []),
×
1250
    file:close(F),
×
1251
    %% Read the default room options defined for the first virtual host
1252
    DefRoomOpts = mod_muc_opt:default_room_options(ejabberd_config:get_myname()),
×
1253
    [muc_create_room(ejabberd_config:get_myname(), A, DefRoomOpts) || A <- Rooms],
×
1254
    ok.
×
1255

1256

1257
%%---------------------------------
1258
%% List/Delete Unused/Empty Rooms
1259
%%---------------------------------
1260

1261
%%---------------
1262
%% Control
1263

1264
rooms_unused_list(Service, Days) ->
1265
    rooms_report(unused, list, Service, Days).
×
1266
rooms_unused_destroy(Service, Days) ->
1267
    rooms_report(unused, destroy, Service, Days).
×
1268

1269
rooms_empty_list(Service) ->
1270
    rooms_report(empty, list, Service, 0).
×
1271
rooms_empty_destroy(Service) ->
1272
    rooms_report(empty, destroy, Service, 0).
×
1273

1274
rooms_empty_destroy_restuple(Service) ->
1275
    DestroyedRooms = rooms_report(empty, destroy, Service, 0),
×
1276
    NumberBin = integer_to_binary(length(DestroyedRooms)),
×
1277
    {ok, <<"Destroyed rooms: ", NumberBin/binary>>}.
×
1278

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

1284
muc_unused(Method, Action, Service, Last_allowed) ->
1285
    %% Get all required info about all existing rooms
1286
    Rooms_all = get_all_rooms(Service, erlang:system_time(microsecond) - Last_allowed*24*60*60*1000),
×
1287

1288
    %% Decide which ones pass the requirements
1289
    Rooms_pass = decide_rooms(Method, Rooms_all, Last_allowed),
×
1290

1291
    Num_rooms_all = length(Rooms_all),
×
1292
    Num_rooms_pass = length(Rooms_pass),
×
1293

1294
    %% Perform the desired action for matching rooms
1295
    act_on_rooms(Method, Action, Rooms_pass),
×
1296

1297
    {Num_rooms_all, Num_rooms_pass, Rooms_pass}.
×
1298

1299
%%---------------
1300
%% Get info
1301

1302
get_online_rooms(ServiceArg) ->
1303
    Hosts = find_services(ServiceArg),
×
1304
    lists:flatmap(
×
1305
      fun(Host) ->
1306
          ServerHost = get_room_serverhost(Host),
×
1307
          [{RoomName, RoomHost, ServerHost, Pid}
×
1308
           || {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
×
1309
      end, Hosts).
1310

1311
get_all_rooms(ServiceArg, Timestamp) ->
1312
    Hosts = find_services(ServiceArg),
×
1313
    lists:flatmap(
×
1314
      fun(Host) ->
1315
              get_all_rooms2(Host, Timestamp)
×
1316
      end, Hosts).
1317

1318
get_all_rooms2(Host, Timestamp) ->
1319
    ServerHost = ejabberd_router:host_of_route(Host),
×
1320
    OnlineRooms = get_online_rooms(Host),
×
1321
    OnlineMap = lists:foldl(
×
1322
        fun({Room, _, _, _}, Map) ->
1323
            Map#{Room => 1}
×
1324
        end, #{}, OnlineRooms),
1325

1326
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
×
1327
    DbRooms =
×
1328
    case {erlang:function_exported(Mod, get_rooms_without_subscribers, 2),
1329
          erlang:function_exported(Mod, get_hibernated_rooms_older_than, 3)} of
1330
        {_, true} ->
1331
            Mod:get_hibernated_rooms_older_than(ServerHost, Host, Timestamp);
×
1332
        {true, _} ->
1333
            Mod:get_rooms_without_subscribers(ServerHost, Host);
×
1334
        _ ->
1335
            Mod:get_rooms(ServerHost, Host)
×
1336
    end,
1337
    StoredRooms = lists:filtermap(
×
1338
        fun(#muc_room{name_host = {Room, _}, opts = Opts}) ->
1339
            case maps:is_key(Room, OnlineMap) of
×
1340
                true ->
1341
                    false;
×
1342
                _ ->
1343
                    {true, {Room, Host, ServerHost, Opts}}
×
1344
            end
1345
        end, DbRooms),
1346
    OnlineRooms ++ StoredRooms.
×
1347

1348
get_room_config(Room_pid) ->
1349
    {ok, R} = mod_muc_room:get_config(Room_pid),
×
1350
    R.
×
1351

1352
get_room_state(Room_pid) ->
1353
    {ok, R} = mod_muc_room:get_state(Room_pid),
×
1354
    R.
×
1355

1356
%%---------------
1357
%% Decide
1358

1359
decide_rooms(Method, Rooms, Last_allowed) ->
1360
    Decide = fun(R) -> decide_room(Method, R, Last_allowed) end,
×
1361
    lists:filter(Decide, Rooms).
×
1362

1363
decide_room(unused, {_Room_name, _Host, ServerHost, Room_pid}, Last_allowed) ->
1364
    NodeStartTime = erlang:system_time(microsecond) -
×
1365
                    1000000*(erlang:monotonic_time(second)-ejabberd_config:get_node_start()),
1366
    OnlyHibernated = case mod_muc_opt:hibernation_timeout(ServerHost) of
×
1367
        Value when Value < Last_allowed*24*60*60*1000 ->
1368
            true;
×
1369
        _ ->
1370
            false
×
1371
        end,
1372
    {Just_created, Num_users} =
×
1373
    case Room_pid of
1374
        Pid when is_pid(Pid) andalso OnlyHibernated ->
1375
            {erlang:system_time(microsecond), 0};
×
1376
        Pid when is_pid(Pid) ->
1377
            case mod_muc_room:get_state(Room_pid) of
×
1378
                {ok, #state{just_created = JC, users = U}} ->
1379
                    {JC, maps:size(U)};
×
1380
                _ ->
1381
                    {erlang:system_time(microsecond), 0}
×
1382
            end;
1383
        Opts ->
1384
            case lists:keyfind(hibernation_time, 1, Opts) of
×
1385
                false ->
1386
                    {NodeStartTime, 0};
×
1387
                {_, undefined} ->
1388
                    {NodeStartTime, 0};
×
1389
                {_, T} ->
1390
                    {T, 0}
×
1391
            end
1392
    end,
1393
    Last = case Just_created of
×
1394
               true ->
1395
                   0;
×
1396
               _ ->
1397
                   (erlang:system_time(microsecond)
1398
                    - Just_created) div 1000000
×
1399
           end,
1400
    case {Num_users, seconds_to_days(Last)} of
×
1401
        {0, Last_days} when (Last_days >= Last_allowed) ->
1402
            true;
×
1403
        _ ->
1404
            false
×
1405
    end;
1406
decide_room(empty, {Room_name, Host, ServerHost, Room_pid}, _Last_allowed) ->
1407
    case gen_mod:is_loaded(ServerHost, mod_mam) of
×
1408
        true ->
1409
            Room_options = case Room_pid of
×
1410
                               _ when is_pid(Room_pid) ->
1411
                                   get_room_options(Room_pid);
×
1412
                               Opts ->
1413
                                   Opts
×
1414
                           end,
1415
            case lists:keyfind(<<"mam">>, 1, Room_options) of
×
1416
                {<<"mam">>, <<"true">>} ->
1417
                    mod_mam:is_empty_for_room(ServerHost, Room_name, Host);
×
1418
                _ ->
1419
                    false
×
1420
            end;
1421
        _ ->
1422
            false
×
1423
    end.
1424

1425
seconds_to_days(S) ->
1426
    S div (60*60*24).
×
1427

1428
%%---------------
1429
%% Act
1430

1431
act_on_rooms(Method, Action, Rooms) ->
1432
    Delete = fun(Room) ->
×
1433
                     act_on_room(Method, Action, Room)
×
1434
             end,
1435
    lists:foreach(Delete, Rooms).
×
1436

1437
act_on_room(Method, destroy, {N, H, _SH, Pid}) ->
1438
    Message = iolist_to_binary(io_lib:format(
×
1439
        <<"Room destroyed by rooms_~s_destroy.">>, [Method])),
1440
    case Pid of
×
1441
        V when is_pid(V) ->
1442
            mod_muc_room:destroy(Pid, Message);
×
1443
        _ ->
1444
            case get_room_pid(N, H) of
×
1445
                Pid2 when is_pid(Pid2) ->
1446
                    mod_muc_room:destroy(Pid2, Message);
×
1447
                _ ->
1448
                    ok
×
1449
            end
1450
    end;
1451
act_on_room(_Method, list, _) ->
1452
    ok.
×
1453

1454

1455
%%----------------------------
1456
%% Change Room Option
1457
%%----------------------------
1458

1459
get_room_occupants(Room, Host) ->
1460
    case get_room_pid_validate(Room, Host, <<"name">>, <<"service">>) of
×
1461
        {Pid, _, _} when is_pid(Pid) -> get_room_occupants(Pid);
×
1462
        _ -> throw({error, room_not_found})
×
1463
    end.
1464

1465
get_room_occupants(Pid) ->
1466
    S = get_room_state(Pid),
×
1467
    lists:map(
×
1468
      fun({_LJID, Info}) ->
1469
              {jid:encode(Info#user.jid),
×
1470
               Info#user.nick,
1471
               atom_to_list(Info#user.role)}
1472
      end,
1473
      maps:to_list(S#state.users)).
1474

1475
get_room_occupants_number(Room, Host) ->
1476
    case get_room_pid_validate(Room, Host, <<"name">>, <<"service">>) of
×
1477
        {Pid, _, _} when is_pid(Pid )->
1478
            {ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid),
×
1479
            N;
×
1480
        _ ->
1481
            throw({error, room_not_found})
×
1482
    end.
1483

1484
%%----------------------------
1485
%% Send Direct Invitation
1486
%%----------------------------
1487
%% http://xmpp.org/extensions/xep-0249.html
1488

1489
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) when is_binary(UsersString) ->
1490
    UsersStrings = binary:split(UsersString, <<":">>, [global]),
×
1491
    send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings);
×
1492
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings) ->
1493
    case jid:make(RoomName, RoomService) of
×
1494
        error ->
1495
            throw({error, "Invalid 'roomname' or 'service'"});
×
1496
        RoomJid ->
1497
            XmlEl = build_invitation(Password, Reason, RoomJid),
×
1498
            Users = get_users_to_invite(RoomJid, UsersStrings),
×
1499
            [send_direct_invitation(RoomJid, UserJid, XmlEl)
×
1500
             || UserJid <- Users],
×
1501
            ok
×
1502
    end.
1503

1504
get_users_to_invite(RoomJid, UsersStrings) ->
1505
    OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
×
1506
                                         RoomJid#jid.lserver),
1507
    OccupantsJids = [jid:decode(JidString)
×
1508
                     || {JidString, _Nick, _} <- OccupantsTuples],
×
1509
    lists:filtermap(
×
1510
      fun(UserString) ->
1511
              UserJid = jid:decode(UserString),
×
1512
              Val = lists:all(fun(OccupantJid) ->
×
1513
                                      UserJid#jid.luser /= OccupantJid#jid.luser
×
1514
                                          orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
×
1515
                              end,
1516
                              OccupantsJids),
1517
              case {UserJid#jid.luser, Val} of
×
1518
                  {<<>>, _} -> false;
×
1519
                  {_, true} -> {true, UserJid};
×
1520
                  _ -> false
×
1521
              end
1522
      end,
1523
      UsersStrings).
1524

1525
build_invitation(Password, Reason, RoomJid) ->
1526
    Invite = #x_conference{jid = RoomJid,
×
1527
                           password = case Password of
1528
                                          <<"none">> -> <<>>;
×
1529
                                          _ -> Password
×
1530
                                      end,
1531
                           reason = case Reason of
1532
                                        <<"none">> -> <<>>;
×
1533
                                        _ -> Reason
×
1534
                                    end},
1535
    #message{sub_els = [Invite]}.
×
1536

1537
send_direct_invitation(FromJid, UserJid, Msg) ->
1538
    ejabberd_router:route(xmpp:set_from_to(Msg, FromJid, UserJid)).
×
1539

1540
%%----------------------------
1541
%% Change Room Option
1542
%%----------------------------
1543

1544
-spec change_room_option(Name::binary(), Service::binary(), Option::binary(),
1545
                         Value::atom() | integer() | string()) -> ok | mod_muc_log_not_enabled.
1546
%% @doc Change an option in an existing room.
1547
%% Requires the name of the room, the MUC service where it exists,
1548
%% the option to change (for example title or max_users),
1549
%% and the value to assign to the new option.
1550
%% For example:
1551
%% `change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)'
1552
change_room_option(Name, Service, OptionString, ValueString) ->
1553
    case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
×
1554
        {room_not_found, _, _} ->
1555
            throw({error, "Room not found"});
×
1556
        {Pid, _, _} ->
1557
            {Option, Value} = format_room_option(OptionString, ValueString),
×
1558
            change_room_option(Pid, Option, Value)
×
1559
    end.
1560

1561
change_room_option(Pid, Option, Value) ->
1562
    case {Option,
×
1563
          gen_mod:is_loaded((get_room_state(Pid))#state.server_host, mod_muc_log)} of
1564
        {logging, false} ->
1565
            mod_muc_log_not_enabled;
×
1566
        _ ->
1567
            Config = get_room_config(Pid),
×
1568
            Config2 = change_option(Option, Value, Config),
×
1569
            {ok, _} = mod_muc_room:set_config(Pid, Config2),
×
1570
            ok
×
1571
    end.
1572

1573
format_room_option(OptionString, ValueString) ->
1574
    Option = misc:binary_to_atom(OptionString),
×
1575
    Value = case Option of
×
1576
                title -> ValueString;
×
1577
                description -> ValueString;
×
1578
                password -> ValueString;
×
1579
                subject ->ValueString;
×
1580
                subject_author ->ValueString;
×
1581
                presence_broadcast ->misc:expr_to_term(ValueString);
×
1582
                max_users -> binary_to_integer(ValueString);
×
1583
                voice_request_min_interval -> binary_to_integer(ValueString);
×
1584
                vcard -> ValueString;
×
1585
                vcard_xupdate when ValueString /= <<"undefined">>,
1586
                                   ValueString /= <<"external">> ->
1587
                    ValueString;
×
1588
                lang -> ValueString;
×
1589
                pubsub -> ValueString;
×
1590
                affiliations ->
1591
                    [parse_affiliation_string(Opt) || Opt <- str:tokens(ValueString, <<",">>)];
×
1592
                subscribers ->
1593
                    [parse_subscription_string(Opt) || Opt <- str:tokens(ValueString, <<",">>)];
×
1594
                _ -> misc:binary_to_atom(ValueString)
×
1595
            end,
1596
    {Option, Value}.
×
1597

1598
parse_affiliation_string(String) ->
1599
    {Type, JidS} = case String of
×
1600
                       <<"owner:", Jid/binary>> -> {owner, Jid};
×
1601
                       <<"admin:", Jid/binary>> -> {admin, Jid};
×
1602
                       <<"member:", Jid/binary>> -> {member, Jid};
×
1603
                       <<"outcast:", Jid/binary>> -> {outcast, Jid};
×
1604
                       _ -> throw({error, "Invalid 'affiliation'"})
×
1605
                   end,
1606
    try jid:decode(JidS) of
×
1607
        #jid{luser = U, lserver = S, lresource = R} ->
1608
            {{U, S, R}, {Type, <<>>}}
×
1609
    catch _:{bad_jid, _} ->
1610
        throw({error, "Malformed JID in affiliation"})
×
1611
    end.
1612

1613
parse_subscription_string(String) ->
1614
    case str:tokens(String, <<":">>) of
×
1615
        [_] ->
1616
            throw({error, "Invalid 'subscribers' - missing nick"});
×
1617
        [_, _] ->
1618
            throw({error, "Invalid 'subscribers' - missing nodes"});
×
1619
        [JidS, Nick | Nodes] ->
1620
            Nodes2 = parse_nodes(Nodes, []),
×
1621
            try jid:decode(JidS) of
×
1622
                Jid ->
1623
                    {Jid, Nick, Nodes2}
×
1624
            catch _:{bad_jid, _} ->
1625
                throw({error, "Malformed JID in 'subscribers'"})
×
1626
            end
1627
    end.
1628

1629
parse_nodes([], Acc) ->
1630
    Acc;
×
1631
parse_nodes([<<"presence">> | Rest], Acc) ->
1632
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PRESENCE | Acc]);
×
1633
parse_nodes([<<"messages">> | Rest], Acc) ->
1634
    parse_nodes(Rest, [?NS_MUCSUB_NODES_MESSAGES | Acc]);
×
1635
parse_nodes([<<"participants">> | Rest], Acc) ->
1636
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PARTICIPANTS | Acc]);
×
1637
parse_nodes([<<"affiliations">> | Rest], Acc) ->
1638
    parse_nodes(Rest, [?NS_MUCSUB_NODES_AFFILIATIONS | Acc]);
×
1639
parse_nodes([<<"subject">> | Rest], Acc) ->
1640
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBJECT | Acc]);
×
1641
parse_nodes([<<"config">> | Rest], Acc) ->
1642
    parse_nodes(Rest, [?NS_MUCSUB_NODES_CONFIG | Acc]);
×
1643
parse_nodes([<<"system">> | Rest], Acc) ->
1644
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SYSTEM | Acc]);
×
1645
parse_nodes([<<"subscribers">> | Rest], Acc) ->
1646
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBSCRIBERS | Acc]);
×
1647
parse_nodes(_, _) ->
1648
    throw({error, "Invalid 'subscribers' - unknown node name used"}).
×
1649

1650
-spec get_room_pid_validate(binary(), binary(), binary(), binary()) ->
1651
    {pid() | room_not_found, binary(), binary()}.
1652
get_room_pid_validate(Name, Service, NameArg, ServiceArg) ->
1653
    Name2 = validate_room(Name, NameArg),
6✔
1654
    {ServerHost, Service2} = validate_muc2(Service, ServiceArg),
6✔
1655
    case mod_muc:unhibernate_room(ServerHost, Service2, Name2) of
6✔
1656
        error ->
1657
            {room_not_found, Name2, Service2};
×
1658
        {ok, Pid} ->
1659
            {Pid, Name2, Service2}
6✔
1660
    end.
1661

1662
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
1663
-spec get_room_pid(binary(), binary()) -> pid() | room_not_found | invalid_service | unknown_service.
1664
get_room_pid(Name, Service) ->
1665
    try get_room_serverhost(Service) of
×
1666
        ServerHost ->
1667
            case mod_muc:unhibernate_room(ServerHost, Service, Name) of
×
1668
                error ->
1669
                    room_not_found;
×
1670
                {ok, Pid} ->
1671
                    Pid
×
1672
            end
1673
    catch
1674
        error:{invalid_domain, _} ->
1675
            invalid_service;
×
1676
        error:{unregistered_route, _} ->
1677
            unknown_service
×
1678
    end.
1679

1680
room_diagnostics(Name, Service) ->
1681
    try get_room_serverhost(Service) of
×
1682
        ServerHost ->
1683
            RMod = gen_mod:ram_db_mod(ServerHost, mod_muc),
×
1684
            case RMod:find_online_room(ServerHost, Name, Service) of
×
1685
                error ->
1686
                    room_hibernated;
×
1687
                {ok, Pid} ->
1688
                    case rpc:pinfo(Pid, [current_stacktrace, message_queue_len, messages]) of
×
1689
                        [{_, R}, {_, QL}, {_, Q}] ->
1690
                            #{stacktrace => R, queue_size => QL, queue => lists:sublist(Q, 10)};
×
1691
                        _ ->
1692
                            unable_to_probe_process
×
1693
                    end
1694
            end
1695
    catch
1696
        error:{invalid_domain, _} ->
1697
            invalid_service;
×
1698
        error:{unregistered_route, _} ->
1699
            unknown_service
×
1700
    end.
1701

1702
%% It is required to put explicitly all the options because
1703
%% the record elements are replaced at compile time.
1704
%% So, this can't be parametrized.
1705
change_option(Option, Value, Config) ->
1706
    case Option of
×
1707
        allow_change_subj -> Config#config{allow_change_subj = Value};
×
1708
        allowpm -> Config#config{allowpm = Value};
×
1709
        allow_private_messages_from_visitors -> Config#config{allow_private_messages_from_visitors = Value};
×
1710
        allow_query_users -> Config#config{allow_query_users = Value};
×
1711
        allow_subscription -> Config#config{allow_subscription = Value};
×
1712
        allow_user_invites -> Config#config{allow_user_invites = Value};
×
1713
        allow_visitor_nickchange -> Config#config{allow_visitor_nickchange = Value};
×
1714
        allow_visitor_status -> Config#config{allow_visitor_status = Value};
×
1715
        allow_voice_requests -> Config#config{allow_voice_requests = Value};
×
1716
        anonymous -> Config#config{anonymous = Value};
×
1717
        captcha_protected -> Config#config{captcha_protected = Value};
×
1718
        description -> Config#config{description = Value};
×
1719
        lang -> Config#config{lang = Value};
×
1720
        logging -> Config#config{logging = Value};
×
1721
        mam -> Config#config{mam = Value};
×
1722
        max_users -> Config#config{max_users = Value};
×
1723
        members_by_default -> Config#config{members_by_default = Value};
×
1724
        members_only -> Config#config{members_only = Value};
×
1725
        moderated -> Config#config{moderated = Value};
×
1726
        password -> Config#config{password = Value};
×
1727
        password_protected -> Config#config{password_protected = Value};
×
1728
        persistent -> Config#config{persistent = Value};
×
1729
        presence_broadcast -> Config#config{presence_broadcast = Value};
×
1730
        public -> Config#config{public = Value};
×
1731
        public_list -> Config#config{public_list = Value};
×
1732
        pubsub -> Config#config{pubsub = Value};
×
1733
        title -> Config#config{title = Value};
×
1734
        vcard -> Config#config{vcard = Value};
×
1735
        vcard_xupdate -> Config#config{vcard_xupdate = Value};
×
1736
        voice_request_min_interval -> Config#config{voice_request_min_interval = Value}
×
1737
    end.
1738

1739
%%----------------------------
1740
%% Get Room Options
1741
%%----------------------------
1742

1743
get_room_options(Name, Service) ->
1744
    case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
×
1745
        {Pid, _, _} when is_pid(Pid) -> get_room_options(Pid);
×
1746
        _ -> []
×
1747
    end.
1748

1749
get_room_options(Pid) ->
1750
    Config = get_room_config(Pid),
×
1751
    get_options(Config).
×
1752

1753
get_options(Config) ->
1754
    Fields = [misc:atom_to_binary(Field) || Field <- record_info(fields, config)],
×
1755
    [config | ValuesRaw] = tuple_to_list(Config),
×
1756
    Values = lists:map(fun(V) when is_atom(V) -> misc:atom_to_binary(V);
×
1757
                          (V) when is_integer(V) -> integer_to_binary(V);
×
1758
                          (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
×
1759
                          (V) -> V end, ValuesRaw),
×
1760
    lists:zip(Fields, Values).
×
1761

1762
%%----------------------------
1763
%% Get Room Affiliations
1764
%%----------------------------
1765

1766
%% @spec(Name::binary(), Service::binary()) ->
1767
%%    [{JID::string(), Domain::string(), Role::string(), Reason::string()}]
1768
%% @doc Get the affiliations of  the room Name@Service.
1769
get_room_affiliations(Name, Service) ->
1770
    case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
×
1771
        {Pid, _, _} when is_pid(Pid) ->
1772
            %% Get the PID of the online room, then request its state
1773
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
1774
            Affiliations = maps:to_list(StateData#state.affiliations),
×
1775
            lists:map(
×
1776
              fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
1777
                      {Uname, Domain, Aff, Reason};
×
1778
                 ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
1779
                      {Uname, Domain, Aff, <<>>}
×
1780
              end, Affiliations);
1781
        _ ->
1782
            throw({error, "The room does not exist."})
×
1783
    end.
1784

1785
get_room_history(Name, Service) ->
1786
    case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
×
1787
        {Pid, _, _} when is_pid(Pid) ->
1788
            case mod_muc_room:get_state(Pid) of
×
1789
                {ok, StateData} ->
1790
                    History = p1_queue:to_list((StateData#state.history)#lqueue.queue),
×
1791
                    lists:map(
×
1792
                        fun({_Nick, Packet, _HaveSubject, TimeStamp, _Size}) ->
1793
                            {xmpp_util:encode_timestamp(TimeStamp),
×
1794
                             ejabberd_web_admin:pretty_print_xml(xmpp:encode(Packet))}
1795
                        end, History);
1796
                _ ->
1797
                    throw({error, "Unable to fetch room state."})
×
1798
            end;
1799
        _ ->
1800
            throw({error, "The room does not exist."})
×
1801
    end.
1802

1803
%%----------------------------
1804
%% Get Room Affiliation
1805
%%----------------------------
1806

1807
%% @spec(Name::binary(), Service::binary(), JID::binary()) ->
1808
%%    {Affiliation::string()}
1809
%% @doc Get affiliation of a user in the room Name@Service.
1810

1811
get_room_affiliation(Name, Service, JID) ->
1812
    case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
×
1813
        {Pid, _, _} when is_pid(Pid) ->
1814
            %% Get the PID of the online room, then request its state
1815
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
1816
            UserJID = jid:decode(JID),
×
1817
            mod_muc_room:get_affiliation(UserJID, StateData);
×
1818
        _ ->
1819
            throw({error, "The room does not exist."})
×
1820
    end.
1821

1822
%%----------------------------
1823
%% Change Room Affiliation
1824
%%----------------------------
1825

1826
%% @spec(Name, Service, JID, AffiliationString) -> ok | {error, Error}
1827
%%       Name = binary()
1828
%%       Service = binary()
1829
%%       JID = binary()
1830
%%       AffiliationString = "outcast" | "none" | "member" | "admin" | "owner"
1831
%% @doc Set the affiliation of JID in the room Name@Service.
1832
%% If the affiliation is 'none', the action is to remove,
1833
%% In any other case the action will be to create the affiliation.
1834
set_room_affiliation(Name, Service, JID, AffiliationString) ->
1835
    Affiliation = case AffiliationString of
6✔
1836
                      <<"outcast">> -> outcast;
×
1837
                      <<"none">> -> none;
×
1838
                      <<"member">> -> member;
6✔
1839
                      <<"admin">> -> admin;
×
1840
                      <<"owner">> -> owner;
×
1841
                      _ ->
1842
                          throw({error, "Invalid affiliation"})
×
1843
                  end,
1844
    case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
6✔
1845
        {Pid, _, _} when is_pid(Pid) ->
1846
            %% Get the PID for the online room so we can get the state of the room
1847
            case mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>) of
6✔
1848
                {ok, _} ->
1849
                    ok;
6✔
1850
                {error, notfound} ->
1851
                    throw({error, "Room doesn't exists"});
×
1852
                {error, _} ->
1853
                    throw({error, "Unable to perform change"})
×
1854
            end;
1855
        _ ->
1856
            throw({error, "Room doesn't exists"})
×
1857
    end.
1858

1859
%%%
1860
%%% MUC Subscription
1861
%%%
1862

1863
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
1864
    throw({error, "Nickname must be set"});
×
1865
subscribe_room(User, Nick, Room, Nodes) when is_binary(Nodes) ->
1866
    NodeList = re:split(Nodes, "\\h*,\\h*"),
×
1867
    subscribe_room(User, Nick, Room, NodeList);
×
1868
subscribe_room(User, Nick, Room, NodeList) ->
1869
    try jid:decode(Room) of
×
1870
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
1871
            try jid:decode(User) of
×
1872
                UserJID1 ->
1873
                    UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
×
1874
                    case get_room_pid_validate(Name, Host, <<"name">>, <<"room">>) of
×
1875
                        {Pid, _, _} when is_pid(Pid) ->
1876
                            case mod_muc_room:subscribe(
×
1877
                                   Pid, UserJID, Nick, NodeList) of
1878
                                {ok, SubscribedNodes} ->
1879
                                    SubscribedNodes;
×
1880
                                {error, Reason} ->
1881
                                    throw({error, binary_to_list(Reason)})
×
1882
                            end;
1883
                        _ ->
1884
                            throw({error, "The room does not exist"})
×
1885
                    end
1886
            catch _:{bad_jid, _} ->
1887
                    throw({error, "Malformed user JID"})
×
1888
            end;
1889
        _ ->
1890
            throw({error, "Malformed room JID"})
×
1891
    catch _:{bad_jid, _} ->
1892
            throw({error, "Malformed room JID"})
×
1893
    end.
1894

1895
subscribe_room_many(Users, Room, Nodes) ->
1896
    MaxUsers = mod_muc_admin_opt:subscribe_room_many_max_users(global),
×
1897
    if
×
1898
        length(Users) > MaxUsers ->
1899
            throw({error, "Too many users in subscribe_room_many command"});
×
1900
        true ->
1901
            lists:foreach(
×
1902
              fun({User, Nick}) ->
1903
                      subscribe_room(User, Nick, Room, Nodes)
×
1904
              end, Users)
1905
    end.
1906

1907
unsubscribe_room(User, Room) ->
1908
    try jid:decode(Room) of
×
1909
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
1910
            try jid:decode(User) of
×
1911
                UserJID ->
1912
                    case get_room_pid_validate(Name, Host, <<"name">>, <<"room">>) of
×
1913
                        {Pid, _, _} when is_pid(Pid) ->
1914
                            case mod_muc_room:unsubscribe(Pid, UserJID) of
×
1915
                                ok ->
1916
                                    ok;
×
1917
                                {error, Reason} ->
1918
                                    throw({error, binary_to_list(Reason)})
×
1919
                            end;
1920
                        _ ->
1921
                            throw({error, "The room does not exist"})
×
1922
                    end
1923
            catch _:{bad_jid, _} ->
1924
                    throw({error, "Malformed user JID"})
×
1925
            end;
1926
        _ ->
1927
            throw({error, "Malformed room JID"})
×
1928
    catch _:{bad_jid, _} ->
1929
            throw({error, "Malformed room JID"})
×
1930
    end.
1931

1932
get_subscribers(Name, Host) ->
1933
    case get_room_pid_validate(Name, Host, <<"name">>, <<"service">>) of
×
1934
        {Pid, _, _} when is_pid(Pid) ->
1935
            {ok, JIDList} = mod_muc_room:get_subscribers(Pid),
×
1936
            [jid:encode(jid:remove_resource(J)) || J <- JIDList];
×
1937
        _ ->
1938
            throw({error, "The room does not exist"})
×
1939
    end.
1940

1941
%%----------------------------
1942
%% Utils
1943
%%----------------------------
1944

1945
-spec validate_host(Name :: binary(), ArgName::binary()) -> binary().
1946
validate_host(Name, ArgName) ->
1947
    case jid:nameprep(Name) of
×
1948
        error ->
1949
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
1950
        Name2 ->
1951
            case lists:member(Name2, ejabberd_option:hosts()) of
×
1952
                false ->
1953
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
1954
                _ ->
1955
                    Name2
×
1956
            end
1957
    end.
1958

1959
-spec validate_user(Name :: binary(), ArgName::binary()) -> binary().
1960
validate_user(Name, ArgName) ->
1961
    case jid:nodeprep(Name) of
×
1962
        error ->
1963
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
1964
        Name2 ->
1965
            Name2
×
1966
    end.
1967

1968
-spec validate_muc(Name :: binary(), ArgName::binary()) -> binary().
1969
validate_muc(Name, ArgName) ->
1970
    case jid:nameprep(Name) of
×
1971
        error ->
1972
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
1973
        Name2 ->
1974
            try get_room_serverhost(Name2) of
×
1975
                _ -> Name2
×
1976
            catch
1977
                error:{invalid_domain, _} ->
1978
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
1979
                error:{unregistered_route, _} ->
1980
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
×
1981
            end
1982
    end.
1983

1984
-spec validate_muc2(Name :: binary(), ArgName::binary()) -> {binary(), binary()}.
1985
validate_muc2(Name, ArgName) ->
1986
    case jid:nameprep(Name) of
6✔
1987
        error ->
1988
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
1989
        Name2 ->
1990
            try get_room_serverhost(Name2) of
6✔
1991
                Host -> {Host, Name2}
6✔
1992
            catch
1993
                error:{invalid_domain, _} ->
1994
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
1995
                error:{unregistered_route, _} ->
1996
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
×
1997
            end
1998
    end.
1999

2000
-spec validate_room(Name :: binary(), ArgName :: binary()) -> binary().
2001
validate_room(Name, ArgName) ->
2002
    case jid:nodeprep(Name) of
6✔
2003
        error ->
2004
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2005
        Name2 ->
2006
            Name2
6✔
2007
    end.
2008

2009
find_service(global) ->
2010
    global;
×
2011
find_service(ServerHost) ->
2012
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
×
2013

2014
find_services_validate(Global, _Name) when Global == global;
2015
    Global == <<"global">> ->
2016
    find_services(Global);
×
2017
find_services_validate(Service, Name) ->
2018
    case validate_muc(Service, Name) of
×
2019
        Service2 -> find_services(Service2)
×
2020
    end.
2021

2022
find_services(Global) when Global == global;
2023
                        Global == <<"global">> ->
2024
    lists:flatmap(
×
2025
      fun(ServerHost) ->
2026
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2027
                  true ->
2028
                      [find_service(ServerHost)];
×
2029
                  false ->
2030
                      []
×
2031
              end
2032
      end, ejabberd_option:hosts());
2033
find_services(Service) when is_binary(Service) ->
2034
    [Service].
×
2035

2036
get_room_serverhost(Service) when is_binary(Service) ->
2037
  ejabberd_router:host_of_route(Service).
6✔
2038

2039
find_host(ServerHost) ->
2040
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
21✔
2041

2042
find_hosts(Global) when Global == global;
2043
                        Global == <<"global">> ->
2044
    lists:flatmap(
×
2045
      fun(ServerHost) ->
2046
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2047
                  true ->
2048
                      [find_host(ServerHost)];
×
2049
                  false ->
2050
                      []
×
2051
              end
2052
      end, ejabberd_option:hosts());
2053
find_hosts(ServerHost) ->
2054
    case gen_mod:is_loaded(ServerHost, mod_muc) of
21✔
2055
        true ->
2056
            [find_host(ServerHost)];
21✔
2057
        false ->
2058
            []
×
2059
    end.
2060

2061
mod_opt_type(subscribe_room_many_max_users) ->
2062
    econf:int().
3✔
2063

2064
mod_options(_) ->
2065
    [{subscribe_room_many_max_users, 50}].
3✔
2066

2067
mod_doc() ->
2068
    #{desc =>
×
2069
          [?T("This module provides commands to administer local MUC "
2070
              "services and their MUC rooms. It also provides simple "
2071
              "WebAdmin pages to view the existing rooms."), "",
2072
           ?T("This module depends on _`mod_muc`_.")],
2073
    opts =>
2074
          [{subscribe_room_many_max_users,
2075
            #{value => ?T("Number"),
2076
              note => "added in 22.05",
2077
              desc =>
2078
                  ?T("How many users can be subscribed to a room at once using "
2079
                     "the 'subscribe_room_many' command. "
2080
                     "The default value is '50'.")}}]}.
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc