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

processone / ejabberd / 900

15 Jan 2025 07:33PM UTC coverage: 33.389% (+0.03%) from 33.364%
900

push

github

badlop
mod_muc: Document MUC room option vcard_xupdate

14859 of 44503 relevant lines covered (33.39%)

622.16 hits per line

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

4.67
/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-2025   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
-module(mod_muc_admin).
27
-author('badlop@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_register_nick/4,
34
         muc_unregister_nick/2, muc_unregister_nick/3,
35
         create_room_with_opts/4, create_room/3, destroy_room/2,
36
         create_rooms_file/1, destroy_rooms_file/1,
37
         rooms_unused_list/2, rooms_unused_destroy/2,
38
         rooms_empty_list/1, rooms_empty_destroy/1, rooms_empty_destroy_restuple/1,
39
         get_user_rooms/2, get_user_subscriptions/2, get_room_occupants/2,
40
         get_room_occupants_number/2, send_direct_invitation/5,
41
         change_room_option/4, get_room_options/2,
42
         set_room_affiliation/4, set_room_affiliation/5, get_room_affiliations/2,
43
         get_room_affiliations_v3/2, get_room_affiliation/3,
44
         subscribe_room/4, subscribe_room/6,
45
         subscribe_room_many/3, subscribe_room_many_v3/4,
46
         unsubscribe_room/2, unsubscribe_room/4, get_subscribers/2,
47
         get_room_serverhost/1,
48
         web_menu_main/2, web_page_main/2,
49
         web_menu_host/3, web_page_host/3,
50
         web_menu_hostuser/4, web_page_hostuser/4,
51
         webadmin_muc/2,
52
         mod_opt_type/1, mod_options/1,
53
         get_commands_spec/0, find_hosts/1, room_diagnostics/2,
54
         get_room_pid/2, get_room_history/2]).
55

56
-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/4]).
57

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

67
%%----------------------------
68
%% gen_mod
69
%%----------------------------
70

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

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

89
reload(_Host, _NewOpts, _OldOpts) ->
90
    ok.
×
91

92
depends(_Host, _Opts) ->
93
    [{mod_muc, hard}].
9✔
94

95
%%%
96
%%% Register commands
97
%%%
98

99
get_commands_spec() ->
100
    [
101
     #ejabberd_commands{name = muc_online_rooms, tags = [muc],
4✔
102
                       desc = "List existing rooms",
103
                       longdesc = "Ask for a specific host, or `global` to use all vhosts.",
104
                       policy = admin,
105
                       module = ?MODULE, function = muc_online_rooms,
106
                       args_desc = ["MUC service, or `global` for all"],
107
                       args_example = ["conference.example.com"],
108
                       result_desc = "List of rooms JIDs",
109
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
110
                       args = [{service, binary}],
111
                       args_rename = [{host, service}],
112
                       result = {rooms, {list, {room, string}}}},
113
        #ejabberd_commands{name = muc_online_rooms_by_regex, tags = [muc],
114
                       desc = "List existing rooms filtered by regexp",
115
                       longdesc = "Ask for a specific host, or `global` to use all vhosts.",
116
                       policy = admin,
117
                       module = ?MODULE, function = muc_online_rooms_by_regex,
118
                       args_desc = ["MUC service, or `global` for all",
119
                                    "Regex pattern for room name"],
120
                       args_example = ["conference.example.com", "^prefix"],
121
                       result_desc = "List of rooms with summary",
122
                       result_example = [{"room1@conference.example.com", "true", 10},
123
                                         {"room2@conference.example.com", "false", 10}],
124
                       args = [{service, binary}, {regex, binary}],
125
                       args_rename = [{host, service}],
126
                       result = {rooms, {list, {room, {tuple,
127
                                                          [{jid, string},
128
                                                           {public, string},
129
                                                           {participants, integer}
130
                                                          ]}}}}},
131
     #ejabberd_commands{name = muc_register_nick, tags = [muc],
132
                       desc = "Register a nick to a User JID in a MUC service",
133
                       module = ?MODULE, function = muc_register_nick,
134
                       args_desc = ["Nick", "User JID", "Service"],
135
                       args_example = [<<"Tim">>, <<"tim@example.org">>, <<"conference.example.org">>],
136
                       args = [{nick, binary}, {jid, binary}, {service, binary}],
137
                       args_rename = [{host, service}],
138
                       result = {res, rescode}},
139
     #ejabberd_commands{name = muc_register_nick, tags = [muc],
140
                       desc = "Register a nick to a User JID in a MUC service",
141
                       module = ?MODULE, function = muc_register_nick,
142
                       version = 3,
143
                       note = "updated in 24.12",
144
                       args_desc = ["nick", "user name", "user host", "MUC service"],
145
                       args_example = [<<"Tim">>, <<"tim">>, <<"example.org">>, <<"conference.example.org">>],
146
                       args = [{nick, binary}, {user, binary}, {host, binary}, {service, binary}],
147
                       args_rename = [{host, service}],
148
                       result = {res, rescode}},
149
     #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
150
                       desc = "Unregister the nick registered by that account in the MUC service",
151
                       module = ?MODULE, function = muc_unregister_nick,
152
                       args_desc = ["User JID", "MUC service"],
153
                       args_example = [<<"tim@example.org">>, <<"conference.example.org">>],
154
                       args = [{jid, binary}, {service, binary}],
155
                       args_rename = [{host, service}],
156
                       result = {res, rescode}},
157
     #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
158
                       desc = "Unregister the nick registered by that account in the MUC service",
159
                       module = ?MODULE, function = muc_unregister_nick,
160
                       version = 3,
161
                       note = "updated in 24.12",
162
                       args_desc = ["user name", "user host", "MUC service"],
163
                       args_example = [<<"tim">>, <<"example.org">>, <<"conference.example.org">>],
164
                       args = [{user, binary}, {host, binary}, {service, binary}],
165
                       args_rename = [{host, service}],
166
                       result = {res, rescode}},
167

168
     #ejabberd_commands{name = create_room, tags = [muc_room],
169
                       desc = "Create a MUC room name@service in host",
170
                       module = ?MODULE, function = create_room,
171
                       args_desc = ["Room name", "MUC service", "Server host"],
172
                       args_example = ["room1", "conference.example.com", "example.com"],
173
                       args = [{room, binary}, {service, binary},
174
                               {host, binary}],
175
                       args_rename = [{name, room}],
176
                       result = {res, rescode}},
177
     #ejabberd_commands{name = destroy_room, tags = [muc_room],
178
                       desc = "Destroy a MUC room",
179
                       module = ?MODULE, function = destroy_room,
180
                       args_desc = ["Room name", "MUC service"],
181
                       args_example = ["room1", "conference.example.com"],
182
                       args = [{room, binary}, {service, binary}],
183
                       args_rename = [{name, room}],
184
                       result = {res, rescode}},
185
     #ejabberd_commands{name = create_rooms_file, tags = [muc],
186
                       desc = "Create the rooms indicated in file",
187
                       longdesc = "Provide one room JID per line. Rooms will be created after restart.",
188
                       note = "improved in 24.12",
189
                       module = ?MODULE, function = create_rooms_file,
190
                       args_desc = ["Path to the text file with one room JID per line"],
191
                       args_example = ["/home/ejabberd/rooms.txt"],
192
                       args = [{file, string}],
193
                       result = {res, rescode}},
194
     #ejabberd_commands{name = create_room_with_opts, tags = [muc_room, muc_sub],
195
                       desc = "Create a MUC room name@service in host with given options",
196
                       longdesc =
197
                        "The syntax of `affiliations` is: `Type:JID,Type:JID`. "
198
                        "The syntax of `subscribers` is: `JID:Nick:Node:Node2:Node3,JID:Nick:Node`.",
199
                       module = ?MODULE, function = create_room_with_opts,
200
                       args_desc = ["Room name", "MUC service", "Server host", "List of options"],
201
                       args_example = ["room1", "conference.example.com", "localhost",
202
                                       [{"members_only","true"},
203
                                        {"affiliations", "owner:bob@example.com,member:peter@example.com"},
204
                                        {"subscribers", "bob@example.com:Bob:messages:subject,anne@example.com:Anne:messages"}]],
205
                       args = [{room, binary}, {service, binary},
206
                               {host, binary},
207
                               {options, {list,
208
                                          {option, {tuple,
209
                                                    [{name, binary},
210
                                                     {value, binary}
211
                                                    ]}}
212
                                         }}],
213
                       args_rename = [{name, room}],
214
                       result = {res, rescode}},
215
     #ejabberd_commands{name = destroy_rooms_file, tags = [muc],
216
                       desc = "Destroy the rooms indicated in file",
217
                       longdesc = "Provide one room JID per line.",
218
                       module = ?MODULE, function = destroy_rooms_file,
219
                       args_desc = ["Path to the text file with one room JID per line"],
220
                       args_example = ["/home/ejabberd/rooms.txt"],
221
                       args = [{file, string}],
222
                       result = {res, rescode}},
223
     #ejabberd_commands{name = rooms_unused_list, tags = [muc],
224
                       desc = "List the rooms that are unused for many days in the service",
225
                       longdesc = "The room recent history is used, so it's recommended "
226
                            " to wait a few days after service start before running this."
227
                            " The MUC service argument can be `global` to get all hosts.",
228
                       module = ?MODULE, function = rooms_unused_list,
229
                       args_desc = ["MUC service, or `global` for all", "Number of days"],
230
                       args_example = ["conference.example.com", 31],
231
                       result_desc = "List of unused rooms",
232
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
233
                       args = [{service, binary}, {days, integer}],
234
                       args_rename = [{host, service}],
235
                       result = {rooms, {list, {room, string}}}},
236
     #ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
237
                       desc = "Destroy the rooms that are unused for many days in the service",
238
                       longdesc = "The room recent history is used, so it's recommended "
239
                            " to wait a few days after service start before running this."
240
                            " The MUC service argument can be `global` to get all hosts.",
241
                       module = ?MODULE, function = rooms_unused_destroy,
242
                       args_desc = ["MUC service, or `global` for all", "Number of days"],
243
                       args_example = ["conference.example.com", 31],
244
                       result_desc = "List of unused rooms that has been destroyed",
245
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
246
                       args = [{service, binary}, {days, integer}],
247
                       args_rename = [{host, service}],
248
                       result = {rooms, {list, {room, string}}}},
249

250
     #ejabberd_commands{name = rooms_empty_list, tags = [muc],
251
                       desc = "List the rooms that have no messages in archive",
252
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
253
                       module = ?MODULE, function = rooms_empty_list,
254
                       args_desc = ["MUC service, or `global` for all"],
255
                       args_example = ["conference.example.com"],
256
                       result_desc = "List of empty rooms",
257
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
258
                       args = [{service, binary}],
259
                       args_rename = [{host, service}],
260
                       result = {rooms, {list, {room, string}}}},
261
     #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
262
                       desc = "Destroy the rooms that have no messages in archive",
263
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
264
                       module = ?MODULE, function = rooms_empty_destroy,
265
                       args_desc = ["MUC service, or `global` for all"],
266
                       args_example = ["conference.example.com"],
267
                       result_desc = "List of empty rooms that have been destroyed",
268
                       result_example = ["room1@conference.example.com", "room2@conference.example.com"],
269
                       args = [{service, binary}],
270
                       args_rename = [{host, service}],
271
                       result = {rooms, {list, {room, string}}}},
272
     #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
273
                       desc = "Destroy the rooms that have no messages in archive",
274
                       longdesc = "The MUC service argument can be `global` to get all hosts.",
275
                       module = ?MODULE, function = rooms_empty_destroy_restuple,
276
                       version = 2,
277
                       note = "modified in 24.06",
278
                       args_desc = ["MUC service, or `global` for all"],
279
                       args_example = ["conference.example.com"],
280
                       result_desc = "List of empty rooms that have been destroyed",
281
                       result_example = {ok, <<"Destroyed rooms: 2">>},
282
                       args = [{service, binary}],
283
                       args_rename = [{host, service}],
284
                       result = {res, restuple}},
285

286
     #ejabberd_commands{name = get_user_rooms, tags = [muc],
287
                        desc = "Get the list of rooms where this user is occupant",
288
                        module = ?MODULE, function = get_user_rooms,
289
                        args_desc = ["Username", "Server host"],
290
                        args_example = ["tom", "example.com"],
291
                        result_example = ["room1@conference.example.com", "room2@conference.example.com"],
292
                        args = [{user, binary}, {host, binary}],
293
                        result = {rooms, {list, {room, string}}}},
294
     #ejabberd_commands{name = get_user_subscriptions, tags = [muc, muc_sub],
295
                        desc = "Get the list of rooms where this user is subscribed",
296
                        note = "added in 21.04",
297
                        module = ?MODULE, function = get_user_subscriptions,
298
                        args_desc = ["Username", "Server host"],
299
                        args_example = ["tom", "example.com"],
300
                        result_example = [{"room1@conference.example.com", "Tommy", ["mucsub:config"]}],
301
                        args = [{user, binary}, {host, binary}],
302
                        result = {rooms,
303
                                  {list,
304
                                   {room,
305
                                    {tuple,
306
                                     [{roomjid, string},
307
                                      {usernick, string},
308
                                      {nodes, {list, {node, string}}}
309
                                     ]}}
310
                                  }}},
311

312
     #ejabberd_commands{name = get_room_occupants, tags = [muc_room],
313
                        desc = "Get the list of occupants of a MUC room",
314
                        module = ?MODULE, function = get_room_occupants,
315
                        args_desc = ["Room name", "MUC service"],
316
                        args_example = ["room1", "conference.example.com"],
317
                        result_desc = "The list of occupants with JID, nick and affiliation",
318
                        result_example = [{"user1@example.com/psi", "User 1", "owner"}],
319
                        args = [{room, binary}, {service, binary}],
320
                        args_rename = [{name, room}],
321
                        result = {occupants, {list,
322
                                              {occupant, {tuple,
323
                                                          [{jid, string},
324
                                                           {nick, string},
325
                                                           {role, string}
326
                                                          ]}}
327
                                             }}},
328

329
     #ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
330
                        desc = "Get the number of occupants of a MUC room",
331
                        module = ?MODULE, function = get_room_occupants_number,
332
                        args_desc = ["Room name", "MUC service"],
333
                        args_example = ["room1", "conference.example.com"],
334
                        result_desc = "Number of room occupants",
335
                        result_example = 7,
336
                        args = [{room, binary}, {service, binary}],
337
                        args_rename = [{name, room}],
338
                        result = {occupants, integer}},
339

340
     #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
341
                        desc = "Send a direct invitation to several destinations",
342
                        longdesc = "Since ejabberd 20.12, this command is "
343
                        "asynchronous: the API call may return before the "
344
                        "server has send all the invitations.\n\n"
345
                        "Password and Message can also be: `none`. "
346
                        "Users JIDs are separated with `:`.",
347
                        module = ?MODULE, function = send_direct_invitation,
348
                        args_desc = ["Room name", "MUC service", "Password, or `none`",
349
                         "Reason text, or `none`", "Users JIDs separated with `:` characters"],
350
                        args_example = [<<"room1">>, <<"conference.example.com">>,
351
                                        <<>>, <<"Check this out!">>,
352
                                        "user2@localhost:user3@example.com"],
353
                        args = [{room, binary}, {service, binary}, {password, binary},
354
                                {reason, binary}, {users, binary}],
355
                        args_rename = [{name, room}],
356
                        result = {res, rescode}},
357
     #ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
358
                        desc = "Send a direct invitation to several destinations",
359
                        longdesc = "Since ejabberd 20.12, this command is "
360
                        "asynchronous: the API call may return before the "
361
                        "server has send all the invitations.\n\n"
362
                        "`password` and `message` can be set to `none`.",
363
                        module = ?MODULE, function = send_direct_invitation,
364
                        version = 1,
365
                        note = "updated in 24.02",
366
                        args_desc = ["Room name", "MUC service", "Password, or `none`",
367
                         "Reason text, or `none`", "List of users JIDs"],
368
                        args_example = [<<"room1">>, <<"conference.example.com">>,
369
                                        <<>>, <<"Check this out!">>,
370
                                        ["user2@localhost", "user3@example.com"]],
371
                        args = [{room, binary}, {service, binary}, {password, binary},
372
                                {reason, binary}, {users, {list, {jid, binary}}}],
373
                        args_rename = [{name, room}],
374
                        result = {res, rescode}},
375

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

562

563
     #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
564
                        desc = "Get the list of affiliations of a MUC room",
565
                        module = ?MODULE, function = get_room_affiliations,
566
                        args_desc = ["Room name", "MUC service"],
567
                        args_example = ["room1", "conference.example.com"],
568
                        result_desc = "The list of affiliations with username, domain, affiliation and reason",
569
                        result_example = [{"user1", "example.com", member, "member"}],
570
                        args = [{name, binary}, {service, binary}],
571
                        result = {affiliations, {list,
572
                                                 {affiliation, {tuple,
573
                                                                [{username, string},
574
                                                                 {domain, string},
575
                                                                 {affiliation, atom},
576
                                                                 {reason, string}
577
                                                                ]}}
578
                                                }}},
579
     #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
580
                        desc = "Get the list of affiliations of a MUC room",
581
                        module = ?MODULE, function = get_room_affiliations_v3,
582
                        version = 3,
583
                        note = "updated in 24.12",
584
                        args_desc = ["Room name", "MUC service"],
585
                        args_example = ["room1", "conference.example.com"],
586
                        result_desc = "The list of affiliations with jid, affiliation and reason",
587
                        result_example = [{"user1@example.com", member, "member"}],
588
                        args = [{room, binary}, {service, binary}],
589
                        result = {affiliations, {list,
590
                                                 {affiliation, {tuple,
591
                                                                [{jid, string},
592
                                                                 {affiliation, atom},
593
                                                                 {reason, string}
594
                                                                ]}}
595
                                                }}},
596

597

598
         #ejabberd_commands{name = get_room_affiliation, tags = [muc_room],
599
                        desc = "Get affiliation of a user in MUC room",
600
                        module = ?MODULE, function = get_room_affiliation,
601
                        args_desc = ["Room name", "MUC service", "User JID"],
602
                        args_example = ["room1", "conference.example.com", "user1@example.com"],
603
                        result_desc = "Affiliation of the user",
604
                        result_example = member,
605
                        args = [{room, binary}, {service, binary}, {jid, binary}],
606
                        args_rename = [{name, room}],
607
                        result = {affiliation, atom}},
608
         #ejabberd_commands{name = get_room_history, tags = [muc_room],
609
                        desc = "Get history of messages stored inside MUC room state",
610
                        note = "added in 23.04",
611
                        module = ?MODULE, function = get_room_history,
612
                        args_desc = ["Room name", "MUC service"],
613
                        args_example = ["room1", "conference.example.com"],
614
                        args = [{room, binary}, {service, binary}],
615
                        args_rename = [{name, room}],
616
                        result = {history, {list,
617
                                            {entry, {tuple,
618
                                                     [{timestamp, string},
619
                                                      {message, string}]}}}}},
620

621
         #ejabberd_commands{name = webadmin_muc, tags = [internal],
622
                        desc = "Generate WebAdmin MUC Rooms HTML",
623
                        module = ?MODULE, function = webadmin_muc,
624
                        args = [{request, any}, {lang, binary}],
625
                        result = {res, any}}
626
        ].
627

628

629
%%%
630
%%% ejabberd commands
631
%%%
632

633
muc_online_rooms(ServiceArg) ->
634
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
635
    lists:flatmap(
×
636
      fun(Host) ->
637
              [<<Name/binary, "@", Host/binary>>
×
638
               || {Name, _, _} <- mod_muc:get_online_rooms(Host)]
×
639
      end, Hosts).
640

641
muc_online_rooms_by_regex(ServiceArg, Regex) ->
642
    {_, P} = re:compile(Regex),
×
643
    Hosts = find_services_validate(ServiceArg, <<"serverhost">>),
×
644
    lists:flatmap(
×
645
      fun(Host) ->
646
              [build_summary_room(Name, RoomHost, Pid)
×
647
               || {Name, RoomHost, Pid} <- mod_muc:get_online_rooms(Host),
×
648
                   is_name_match(Name, P)]
×
649
      end, Hosts).
650

651
is_name_match(Name, P) ->
652
        case re:run(Name, P) of
×
653
                {match, _} -> true;
×
654
                nomatch -> false
×
655
        end.
656

657
build_summary_room(Name, Host, Pid) ->
658
    C = get_room_config(Pid),
×
659
    Public = C#config.public,
×
660
    S = get_room_state(Pid),
×
661
    Participants = maps:size(S#state.users),
×
662
    {<<Name/binary, "@", Host/binary>>,
×
663
         misc:atom_to_binary(Public),
664
     Participants
665
    }.
666

667
muc_register_nick(Nick, User, Host, Service) ->
668
    muc_register_nick(Nick, makeencode(User, Host), Service).
×
669

670
muc_register_nick(Nick, FromBinary, Service) ->
671
    try {get_room_serverhost(Service), jid:decode(FromBinary)} of
×
672
        {ServerHost, From} ->
673
            Lang = <<"en">>,
×
674
            case mod_muc:iq_set_register_info(ServerHost, Service, From, Nick, Lang) of
×
675
                {result, undefined} -> ok;
×
676
                {error, #stanza_error{reason = 'conflict'}} ->
677
                    throw({error, "Nick already registered"});
×
678
                {error, _} ->
679
                    throw({error, "Database error"})
×
680
            end
681
        catch
682
        error:{invalid_domain, _} ->
683
            throw({error, "Invalid value of 'service'"});
×
684
        error:{unregistered_route, _} ->
685
            throw({error, "Unknown host in 'service'"});
×
686
        error:{bad_jid, _} ->
687
            throw({error, "Invalid 'jid'"});
×
688
        _ ->
689
            throw({error, "Internal error"})
×
690
    end.
691

692
muc_unregister_nick(User, Host, Service) ->
693
    muc_unregister_nick(makeencode(User, Host), Service).
×
694

695
muc_unregister_nick(FromBinary, Service) ->
696
    muc_register_nick(<<"">>, FromBinary, Service).
×
697

698
get_user_rooms(User, Server) ->
699
    lists:flatmap(
×
700
      fun(ServerHost) ->
701
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
702
                  true ->
703
                      Rooms = mod_muc:get_online_rooms_by_user(
×
704
                                ServerHost, jid:nodeprep(User), jid:nodeprep(Server)),
705
                      [<<Name/binary, "@", Host/binary>>
×
706
                           || {Name, Host} <- Rooms];
×
707
                  false ->
708
                      []
×
709
              end
710
      end, ejabberd_option:hosts()).
711

712
get_user_subscriptions(User, Server) ->
713
    User2 = validate_user(User, <<"user">>),
×
714
    Server2 = validate_host(Server, <<"host">>),
×
715
    Services = find_services(global),
×
716
    UserJid = jid:make(User2, Server2),
×
717
    lists:flatmap(
×
718
      fun(ServerHost) ->
719
              {ok, Rooms} = mod_muc:get_subscribed_rooms(ServerHost, UserJid),
×
720
              [{jid:encode(RoomJid), UserNick, Nodes}
×
721
               || {RoomJid, UserNick, Nodes} <- Rooms]
×
722
      end, Services).
723

724
%%----------------------------
725
%% Ad-hoc commands
726
%%----------------------------
727

728

729
%%----------------------------
730
%% Web Admin
731
%%----------------------------
732

733
%% @format-begin
734

735
%%---------------
736
%% Web Admin Menu
737

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

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

744
%%---------------
745
%% Web Admin Page
746

747
web_page_main(_, #request{path = [<<"muc">>], lang = Lang} = R) ->
748
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
749
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
750
    Res = [make_command(webadmin_muc, R, [{<<"request">>, R}, {<<"lang">>, Lang}], [])],
×
751
    {stop, Title ++ Res};
×
752
web_page_main(Acc, _) ->
753
    Acc.
×
754

755
web_page_host(_, Host, #request{path = [<<"muc">> | RPath], lang = Lang} = R) ->
756
    PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
×
757
    Service = find_service(Host),
×
758
    Level = length(RPath),
×
759
    Res = webadmin_muc_host(Host, Service, RPath, R, Lang, Level, PageTitle),
×
760
    {stop, Res};
×
761
web_page_host(Acc, _, _) ->
762
    Acc.
×
763

764
%%---------------
765
%% WebAdmin MUC Host Page
766

767
webadmin_muc_host(Host,
768
                  Service,
769
                  [<<"create-room">> | RPath],
770
                  R,
771
                  _Lang,
772
                  Level,
773
                  PageTitle) ->
774
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
775
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Create Room">>, RPath}),
×
776
    Set = [make_command(create_room, R, [{<<"service">>, Service}, {<<"host">>, Host}], []),
×
777
           make_command(create_room_with_opts,
778
                        R,
779
                        [{<<"service">>, Service}, {<<"host">>, Host}],
780
                        [])],
781
    Title ++ Breadcrumb ++ Set;
×
782
webadmin_muc_host(_Host,
783
                  Service,
784
                  [<<"nick-register">> | RPath],
785
                  R,
786
                  _Lang,
787
                  Level,
788
                  PageTitle) ->
789
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
790
    Breadcrumb =
×
791
        make_breadcrumb({service_section, Level, Service, <<"Nick Register">>, RPath}),
792
    Set = [make_command(muc_register_nick, R, [{<<"service">>, Service}], []),
×
793
           make_command(muc_unregister_nick, R, [{<<"service">>, Service}], [])],
794
    Title ++ Breadcrumb ++ Set;
×
795
webadmin_muc_host(_Host,
796
                  Service,
797
                  [<<"rooms-empty">> | RPath],
798
                  R,
799
                  _Lang,
800
                  Level,
801
                  PageTitle) ->
802
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
803
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms Empty">>, RPath}),
×
804
    Set = [make_command(rooms_empty_list,
×
805
                        R,
806
                        [{<<"service">>, Service}],
807
                        [{table_options, {2, RPath}},
808
                         {result_links, [{room, room, 3 + Level, <<"">>}]}]),
809
           make_command(rooms_empty_destroy, R, [{<<"service">>, Service}], [])],
810
    Title ++ Breadcrumb ++ Set;
×
811
webadmin_muc_host(_Host,
812
                  Service,
813
                  [<<"rooms-unused">> | RPath],
814
                  R,
815
                  _Lang,
816
                  Level,
817
                  PageTitle) ->
818
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
819
    Breadcrumb =
×
820
        make_breadcrumb({service_section, Level, Service, <<"Rooms Unused">>, RPath}),
821
    Set = [make_command(rooms_unused_list,
×
822
                        R,
823
                        [{<<"service">>, Service}],
824
                        [{result_links, [{room, room, 3 + Level, <<"">>}]}]),
825
           make_command(rooms_unused_destroy, R, [{<<"service">>, Service}], [])],
826
    Title ++ Breadcrumb ++ Set;
×
827
webadmin_muc_host(_Host,
828
                  Service,
829
                  [<<"rooms-regex">> | RPath],
830
                  R,
831
                  _Lang,
832
                  Level,
833
                  PageTitle) ->
834
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
835
    Breadcrumb =
×
836
        make_breadcrumb({service_section, Level, Service, <<"Rooms by Regex">>, RPath}),
837
    Set = [make_command(muc_online_rooms_by_regex,
×
838
                        R,
839
                        [{<<"service">>, Service}],
840
                        [{result_links, [{jid, room, 3 + Level, <<"">>}]}])],
841
    Title ++ Breadcrumb ++ Set;
×
842
webadmin_muc_host(_Host,
843
                  Service,
844
                  [<<"rooms">>, <<"room">>, Name, <<"affiliations">> | RPath],
845
                  R,
846
                  _Lang,
847
                  Level,
848
                  PageTitle) ->
849
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
850
    Breadcrumb =
×
851
        make_breadcrumb({room_section, Level, Service, <<"Affiliations">>, Name, R, RPath}),
852
    Set = [make_command(set_room_affiliation,
×
853
                        R,
854
                        [{<<"room">>, Name}, {<<"service">>, Service}],
855
                        [])],
856
    Get = [make_command(get_room_affiliations,
×
857
                        R,
858
                        [{<<"room">>, Name}, {<<"service">>, Service}],
859
                        [{table_options, {20, RPath}},
860
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
861
    Title ++ Breadcrumb ++ Get ++ Set;
×
862
webadmin_muc_host(_Host,
863
                  Service,
864
                  [<<"rooms">>, <<"room">>, Name, <<"history">> | RPath],
865
                  R,
866
                  _Lang,
867
                  Level,
868
                  PageTitle) ->
869
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
870
    Breadcrumb =
×
871
        make_breadcrumb({room_section, Level, Service, <<"History">>, Name, R, RPath}),
872
    Get = [make_command(get_room_history,
×
873
                        R,
874
                        [{<<"room">>, Name}, {<<"service">>, Service}],
875
                        [{table_options, {10, RPath}},
876
                         {result_links, [{message, paragraph, 1, <<"">>}]}])],
877
    Title ++ Breadcrumb ++ Get;
×
878
webadmin_muc_host(_Host,
879
                  Service,
880
                  [<<"rooms">>, <<"room">>, Name, <<"invite">> | RPath],
881
                  R,
882
                  _Lang,
883
                  Level,
884
                  PageTitle) ->
885
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
886
    Breadcrumb =
×
887
        make_breadcrumb({room_section, Level, Service, <<"Invite">>, Name, R, RPath}),
888
    Set = [make_command(send_direct_invitation,
×
889
                        R,
890
                        [{<<"room">>, Name}, {<<"service">>, Service}],
891
                        [])],
892
    Title ++ Breadcrumb ++ Set;
×
893
webadmin_muc_host(_Host,
894
                  Service,
895
                  [<<"rooms">>, <<"room">>, Name, <<"occupants">> | RPath],
896
                  R,
897
                  _Lang,
898
                  Level,
899
                  PageTitle) ->
900
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
901
    Breadcrumb =
×
902
        make_breadcrumb({room_section, Level, Service, <<"Occupants">>, Name, R, RPath}),
903
    Get = [make_command(get_room_occupants,
×
904
                        R,
905
                        [{<<"room">>, Name}, {<<"service">>, Service}],
906
                        [{table_options, {20, RPath}},
907
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
908
    Title ++ Breadcrumb ++ Get;
×
909
webadmin_muc_host(_Host,
910
                  Service,
911
                  [<<"rooms">>, <<"room">>, Name, <<"options">> | RPath],
912
                  R,
913
                  _Lang,
914
                  Level,
915
                  PageTitle) ->
916
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
917
    Breadcrumb =
×
918
        make_breadcrumb({room_section, Level, Service, <<"Options">>, Name, R, RPath}),
919
    Set = [make_command(change_room_option,
×
920
                        R,
921
                        [{<<"room">>, Name}, {<<"service">>, Service}],
922
                        [])],
923
    Get = [make_command(get_room_options,
×
924
                        R,
925
                        [{<<"room">>, Name}, {<<"service">>, Service}],
926
                        [])],
927
    Title ++ Breadcrumb ++ Get ++ Set;
×
928
webadmin_muc_host(_Host,
929
                  Service,
930
                  [<<"rooms">>, <<"room">>, Name, <<"subscribers">> | RPath],
931
                  R,
932
                  _Lang,
933
                  Level,
934
                  PageTitle) ->
935
    Title =
×
936
        ?H1GLraw(PageTitle,
937
                 <<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
938
                 <<"MUC/Sub Extension">>),
939
    Breadcrumb =
×
940
        make_breadcrumb({room_section, Level, Service, <<"Subscribers">>, Name, R, RPath}),
941
    Set = [make_command(subscribe_room,
×
942
                        R,
943
                        [{<<"room">>, Name}, {<<"service">>, Service}],
944
                        []),
945
           make_command(unsubscribe_room,
946
                        R,
947
                        [{<<"room">>, Name}, {<<"service">>, Service}],
948
                        [{style, danger}])],
949
    Get = [make_command(get_subscribers,
×
950
                        R,
951
                        [{<<"room">>, Name}, {<<"service">>, Service}],
952
                        [{table_options, {20, RPath}},
953
                         {result_links, [{jid, user, 3 + Level, <<"">>}]}])],
954
    Title ++ Breadcrumb ++ Get ++ Set;
×
955
webadmin_muc_host(_Host,
956
                  Service,
957
                  [<<"rooms">>, <<"room">>, Name, <<"destroy">> | RPath],
958
                  R,
959
                  _Lang,
960
                  Level,
961
                  PageTitle) ->
962
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
963
    Breadcrumb =
×
964
        make_breadcrumb({room_section, Level, Service, <<"Destroy">>, Name, R, RPath}),
965
    Set = [make_command(destroy_room,
×
966
                        R,
967
                        [{<<"room">>, Name}, {<<"service">>, Service}],
968
                        [{style, danger}])],
969
    Title ++ Breadcrumb ++ Set;
×
970
webadmin_muc_host(_Host,
971
                  Service,
972
                  [<<"rooms">>, <<"room">>, Name | _RPath],
973
                  _R,
974
                  Lang,
975
                  Level,
976
                  PageTitle) ->
977
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
978
    Breadcrumb = make_breadcrumb({room, Level, Service, Name}),
×
979
    MenuItems =
×
980
        [{<<"affiliations/">>, <<"Affiliations">>},
981
         {<<"history/">>, <<"History">>},
982
         {<<"invite/">>, <<"Invite">>},
983
         {<<"occupants/">>, <<"Occupants">>},
984
         {<<"options/">>, <<"Options">>},
985
         {<<"subscribers/">>, <<"Subscribers">>},
986
         {<<"destroy/">>, <<"Destroy">>}],
987
    Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
×
988
    Title ++ Breadcrumb ++ Get;
×
989
webadmin_muc_host(_Host, Service, [<<"rooms">> | RPath], R, _Lang, Level, PageTitle) ->
990
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
991
    Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms">>, RPath}),
×
992
    Columns = [<<"jid">>, <<"occupants">>],
×
993
    Rows =
×
994
        lists:map(fun(NameService) ->
995
                     #jid{user = Name} = jid:decode(NameService),
×
996
                     {make_command(echo,
×
997
                                   R,
998
                                   [{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
999
                                   [{only, raw_and_value},
1000
                                    {result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
1001
                      make_command(get_room_occupants_number,
1002
                                   R,
1003
                                   [{<<"room">>, Name}, {<<"service">>, Service}],
1004
                                   [{only, raw_and_value}])}
1005
                  end,
1006
                  make_command_raw_value(muc_online_rooms, R, [{<<"service">>, Service}])),
1007
    Get = [make_command(muc_online_rooms, R, [], [{only, presentation}]),
×
1008
           make_command(get_room_occupants_number, R, [], [{only, presentation}]),
1009
           make_table(20, RPath, Columns, Rows)],
1010
    Title ++ Breadcrumb ++ Get;
×
1011
webadmin_muc_host(_Host, Service, [], _R, Lang, _Level, PageTitle) ->
1012
    Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
×
1013
    Breadcrumb = make_breadcrumb({service, Service}),
×
1014
    MenuItems =
×
1015
        [{<<"create-room/">>, <<"Create Room">>},
1016
         {<<"rooms/">>, <<"Rooms">>},
1017
         {<<"rooms-regex/">>, <<"Rooms by Regex">>},
1018
         {<<"rooms-empty/">>, <<"Rooms Empty">>},
1019
         {<<"rooms-unused/">>, <<"Rooms Unused">>},
1020
         {<<"nick-register/">>, <<"Nick Register">>}],
1021
    Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
×
1022
    Title ++ Breadcrumb ++ Get;
×
1023
webadmin_muc_host(_Host, _Service, _RPath, _R, _Lang, _Level, _PageTitle) ->
1024
    [].
×
1025

1026
make_breadcrumb({service, Service}) ->
1027
    make_breadcrumb([Service]);
×
1028
make_breadcrumb({service_section, Level, Service, Section, RPath}) ->
1029
    make_breadcrumb([{Level, Service}, separator, Section | RPath]);
×
1030
make_breadcrumb({room, Level, Service, Name}) ->
1031
    make_breadcrumb([{Level, Service},
×
1032
                     separator,
1033
                     {Level - 1, <<"Rooms">>},
1034
                     separator,
1035
                     jid:encode({Name, Service, <<"">>})]);
1036
make_breadcrumb({room_section, Level, Service, Section, Name, R, RPath}) ->
1037
    make_breadcrumb([{Level, Service},
×
1038
                     separator,
1039
                     {Level - 1, <<"Rooms">>},
1040
                     separator,
1041
                     make_command(echo,
1042
                                  R,
1043
                                  [{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
1044
                                  [{only, value},
1045
                                   {result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
1046
                     separator,
1047
                     Section
1048
                     | RPath]);
1049
make_breadcrumb(Elements) ->
1050
    lists:map(fun ({xmlel, _, _, _} = Xmlel) ->
×
1051
                      Xmlel;
×
1052
                  (<<"sort">>) ->
1053
                      ?C(<<" +">>);
×
1054
                  (<<"page">>) ->
1055
                      ?C(<<" #">>);
×
1056
                  (separator) ->
1057
                      ?C(<<" > ">>);
×
1058
                  (Bin) when is_binary(Bin) ->
1059
                      ?C(Bin);
×
1060
                  ({Level, Bin}) when is_integer(Level) and is_binary(Bin) ->
1061
                      ?AC(binary:copy(<<"../">>, Level), Bin)
×
1062
              end,
1063
              Elements).
1064

1065
%%---------------
1066
%%
1067

1068
%% Returns: {normal | reverse, Integer}
1069
get_sort_query(Q) ->
1070
    case catch get_sort_query2(Q) of
×
1071
        {ok, Res} ->
1072
            Res;
×
1073
        _ ->
1074
            {normal, 1}
×
1075
    end.
1076

1077
get_sort_query2(Q) ->
1078
    {value, {_, Binary}} = lists:keysearch(<<"sort">>, 1, Q),
×
1079
    Integer = list_to_integer(string:strip(binary_to_list(Binary), right, $/)),
×
1080
    case Integer >= 0 of
×
1081
        true ->
1082
            {ok, {normal, Integer}};
×
1083
        false ->
1084
            {ok, {reverse, abs(Integer)}}
×
1085
    end.
1086

1087
webadmin_muc(#request{q = Q} = R, Lang) ->
1088
    {Sort_direction, Sort_column} = get_sort_query(Q),
×
1089
    Host = global,
×
1090
    Service = find_service(Host),
×
1091
    Rooms_names = get_online_rooms(Service),
×
1092
    Rooms_infos = build_info_rooms(Rooms_names),
×
1093
    Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
×
1094
    Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
×
1095
    TList =
×
1096
        lists:map(fun([RoomJid | Room]) ->
1097
                     JidLink =
×
1098
                         make_command(echo,
1099
                                      R,
1100
                                      [{<<"sentence">>, RoomJid}],
1101
                                      [{only, value},
1102
                                       {result_links, [{sentence, room, 1, <<"">>}]}]),
1103
                     ?XE(<<"tr">>, [?XE(<<"td">>, [JidLink]) | [?XC(<<"td">>, E) || E <- Room]])
×
1104
                  end,
1105
                  Rooms_prepared),
1106
    Titles =
×
1107
        [?T("Jabber ID"),
1108
         ?T("# participants"),
1109
         ?T("Last message"),
1110
         ?T("Public"),
1111
         ?T("Persistent"),
1112
         ?T("Logging"),
1113
         ?T("Just created"),
1114
         ?T("Room title"),
1115
         ?T("Node")],
1116
    {Titles_TR, _} =
×
1117
        lists:mapfoldl(fun(Title, Num_column) ->
1118
                          NCS = integer_to_binary(Num_column),
×
1119
                          TD = ?XE(<<"td">>,
×
1120
                                   [?CT(Title),
1121
                                    ?C(<<" ">>),
1122
                                    ?AC(<<"?sort=", NCS/binary>>, <<"<">>),
1123
                                    ?C(<<" ">>),
1124
                                    ?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
1125
                          {TD, Num_column + 1}
×
1126
                       end,
1127
                       1,
1128
                       Titles),
1129
    [?XCT(<<"h2">>, ?T("Chatrooms")),
×
1130
     ?XE(<<"table">>,
1131
         [?XE(<<"thead">>, [?XE(<<"tr">>, Titles_TR)]), ?XE(<<"tbody">>, TList)])].
1132

1133
sort_rooms(Direction, Column, Rooms) ->
1134
    Rooms2 = lists:keysort(Column, Rooms),
×
1135
    case Direction of
×
1136
        normal ->
1137
            Rooms2;
×
1138
        reverse ->
1139
            lists:reverse(Rooms2)
×
1140
    end.
1141

1142
build_info_rooms(Rooms) ->
1143
    [build_info_room(Room) || Room <- Rooms].
×
1144

1145
build_info_room({Name, Host, _ServerHost, Pid}) ->
1146
    C = get_room_config(Pid),
×
1147
    Title = C#config.title,
×
1148
    Public = C#config.public,
×
1149
    Persistent = C#config.persistent,
×
1150
    Logging = C#config.logging,
×
1151

1152
    S = get_room_state(Pid),
×
1153
    Just_created = S#state.just_created,
×
1154
    Num_participants = maps:size(S#state.users),
×
1155
    Node = node(Pid),
×
1156

1157
    History = S#state.history#lqueue.queue,
×
1158
    Ts_last_message =
×
1159
        case p1_queue:is_empty(History) of
1160
            true ->
1161
                <<"A long time ago">>;
×
1162
            false ->
1163
                Last_message1 = get_queue_last(History),
×
1164
                {_, _, _, Ts_last, _} = Last_message1,
×
1165
                xmpp_util:encode_timestamp(Ts_last)
×
1166
        end,
1167

1168
    {<<Name/binary, "@", Host/binary>>,
×
1169
     Num_participants,
1170
     Ts_last_message,
1171
     Public,
1172
     Persistent,
1173
     Logging,
1174
     Just_created,
1175
     Title,
1176
     Node}.
1177

1178
get_queue_last(Queue) ->
1179
    List = p1_queue:to_list(Queue),
×
1180
    lists:last(List).
×
1181

1182
prepare_rooms_infos(Rooms) ->
1183
    [prepare_room_info(Room) || Room <- Rooms].
×
1184

1185
prepare_room_info(Room_info) ->
1186
    {NameHost,
×
1187
     Num_participants,
1188
     Ts_last_message,
1189
     Public,
1190
     Persistent,
1191
     Logging,
1192
     Just_created,
1193
     Title,
1194
     Node} =
1195
        Room_info,
1196
    [NameHost,
×
1197
     integer_to_binary(Num_participants),
1198
     Ts_last_message,
1199
     misc:atom_to_binary(Public),
1200
     misc:atom_to_binary(Persistent),
1201
     misc:atom_to_binary(Logging),
1202
     justcreated_to_binary(Just_created),
1203
     Title,
1204
     misc:atom_to_binary(Node)].
1205

1206
justcreated_to_binary(J) when is_integer(J) ->
1207
    JNow = misc:usec_to_now(J),
×
1208
    {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(JNow),
×
1209
    str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
×
1210
               [Year, Month, Day, Hour, Minute, Second]);
1211
justcreated_to_binary(J) when is_atom(J) ->
1212
    misc:atom_to_binary(J).
×
1213

1214
%%--------------------
1215
%% Web Admin Host User
1216

1217
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
1218
    Acc
1219
    ++ [{<<"muc-rooms">>, <<"MUC Rooms Online">>},
×
1220
        {<<"muc-affiliations">>, <<"MUC Rooms Affiliations">>},
1221
        {<<"muc-sub">>, <<"MUC Rooms Subscriptions">>},
1222
        {<<"muc-register">>, <<"MUC Service Registration">>}].
1223

1224
web_page_hostuser(_, Host, User, #request{path = [<<"muc-rooms">> | RPath]} = R) ->
1225
    Level = 5 + length(RPath),
×
1226
    Res = ?H1GL(<<"MUC Rooms Online">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1227
          ++ [make_command(get_user_rooms,
1228
                           R,
1229
                           [{<<"user">>, User}, {<<"host">>, Host}],
1230
                           [{table_options, {2, RPath}},
1231
                            {result_links, [{room, room, Level, <<"">>}]}])],
1232
    {stop, Res};
×
1233
web_page_hostuser(_, Host, User, #request{path = [<<"muc-affiliations">>]} = R) ->
1234
    Jid = jid:encode(
×
1235
              jid:make(User, Host)),
1236
    Res = ?H1GL(<<"MUC Rooms Affiliations">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1237
          ++ [make_command(set_room_affiliation, R, [{<<"jid">>, Jid}], []),
1238
              make_command(get_room_affiliation, R, [{<<"jid">>, Jid}], [])],
1239
    {stop, Res};
×
1240
web_page_hostuser(_, Host, User, #request{path = [<<"muc-sub">> | RPath]} = R) ->
1241
    Title =
×
1242
        ?H1GLraw(<<"MUC Rooms Subscriptions">>,
1243
                 <<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
1244
                 <<"MUC/Sub">>),
1245
    Level = 5 + length(RPath),
×
1246
    Set = [make_command(subscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
×
1247
           make_command(unsubscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
1248
    Get = [make_command(get_user_subscriptions,
×
1249
                        R,
1250
                        [{<<"user">>, User}, {<<"host">>, Host}],
1251
                        [{table_options, {20, RPath}},
1252
                         {result_links, [{roomjid, room, Level, <<"">>}]}])],
1253
    {stop, Title ++ Get ++ Set};
×
1254
web_page_hostuser(_, Host, User, #request{path = [<<"muc-register">>]} = R) ->
1255
    Jid = jid:encode(
×
1256
              jid:make(User, Host)),
1257
    Res = ?H1GL(<<"MUC Service Registration">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
×
1258
          ++ [make_command(muc_register_nick, R, [{<<"jid">>, Jid}], []),
1259
              make_command(muc_unregister_nick, R, [{<<"jid">>, Jid}], [])],
1260
    {stop, Res};
×
1261
web_page_hostuser(Acc, _, _, _) ->
1262
    Acc.
×
1263
%% @format-end
1264

1265
%%----------------------------
1266
%% Create/Delete Room
1267
%%----------------------------
1268

1269
-spec create_room(Name::binary(), Host::binary(), ServerHost::binary()) -> ok | error.
1270
%% @doc Create a room immediately with the default options.
1271
create_room(Name1, Host1, ServerHost) ->
1272
    create_room_with_opts(Name1, Host1, ServerHost, []).
×
1273

1274
create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
1275
    ServerHost = validate_host(ServerHost1, <<"serverhost">>),
×
1276
    case get_room_pid_validate(Name1, Host1, <<"service">>) of
×
1277
        {room_not_found, Name, Host} ->
1278
            %% Get the default room options from the muc configuration
1279
            DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
×
1280
            %% Change default room options as required
1281
            FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
×
1282
            RoomOpts = lists:ukeymerge(1,
×
1283
                lists:keysort(1, FormattedRoomOpts),
1284
                lists:keysort(1, DefRoomOpts)),
1285
            case mod_muc:create_room(Host, Name, RoomOpts) of
×
1286
                ok ->
1287
                    ok;
×
1288
                {error, _} ->
1289
                    throw({error, "Unable to start room"})
×
1290
            end;
1291
        _ ->
1292
            throw({error, "Room already exists"})
×
1293
    end.
1294

1295
%% Create the room only in the database.
1296
%% It is required to restart the MUC service for the room to appear.
1297
muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
1298
    io:format("Creating room ~ts@~ts~n", [Name, Host]),
×
1299
    mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts).
×
1300

1301
-spec destroy_room(Name::binary(), Host::binary()) -> ok | {error, room_not_exists}.
1302
%% @doc Destroy the room immediately.
1303
%% If the room has participants, they are not notified that the room was destroyed;
1304
%% they will notice when they try to chat and receive an error that the room doesn't exist.
1305
destroy_room(Name1, Service1) ->
1306
    case get_room_pid_validate(Name1, Service1, <<"service">>) of
×
1307
        {room_not_found, _, _} ->
1308
            throw({error, "Room doesn't exists"});
×
1309
        {Pid, _, _} ->
1310
            mod_muc_room:destroy(Pid),
×
1311
            ok
×
1312
    end.
1313

1314
destroy_room({N, H, SH}) ->
1315
    io:format("Destroying room: ~ts@~ts - vhost: ~ts~n", [N, H, SH]),
×
1316
    destroy_room(N, H).
×
1317

1318

1319
%%----------------------------
1320
%% Destroy Rooms in File
1321
%%----------------------------
1322

1323
%% The format of the file is: one chatroom JID per line
1324
%% The file encoding must be UTF-8
1325

1326
destroy_rooms_file(Filename) ->
1327
    {ok, F} = file:open(Filename, [read]),
×
1328
    RJID = read_room(F),
×
1329
    Rooms = read_rooms(F, RJID, []),
×
1330
    file:close(F),
×
1331
    [destroy_room(A) || A <- Rooms],
×
1332
    ok.
×
1333

1334
read_rooms(_F, eof, L) ->
1335
    L;
×
1336
read_rooms(F, no_room, L) ->
1337
    RJID2 = read_room(F),
×
1338
    read_rooms(F, RJID2, L);
×
1339
read_rooms(F, RJID, L) ->
1340
    RJID2 = read_room(F),
×
1341
    read_rooms(F, RJID2, [RJID | L]).
×
1342

1343
read_room(F) ->
1344
    case io:get_line(F, "") of
×
1345
        eof -> eof;
×
1346
        String ->
1347
            case io_lib:fread("~ts", String) of
×
1348
                {ok, [RoomJID], _} -> split_roomjid(list_to_binary(RoomJID));
×
1349
                {error, What} ->
1350
                    io:format("Parse error: what: ~p~non the line: ~p~n~n", [What, String])
×
1351
            end
1352
    end.
1353

1354
%% This function is quite rudimentary
1355
%% and may not be accurate
1356
split_roomjid(RoomJID) ->
1357
    split_roomjid2(binary:split(RoomJID, <<"@">>)).
×
1358
split_roomjid2([Name, Host]) ->
1359
    [_MUC_service_name, ServerHost] = binary:split(Host, <<".">>),
×
1360
    {Name, Host, ServerHost};
×
1361
split_roomjid2(_) ->
1362
    no_room.
×
1363

1364
%%----------------------------
1365
%% Create Rooms in File
1366
%%----------------------------
1367

1368
create_rooms_file(Filename) ->
1369
    {ok, F} = file:open(Filename, [read]),
×
1370
    RJID = read_room(F),
×
1371
    Rooms = read_rooms(F, RJID, []),
×
1372
    file:close(F),
×
1373
    HostsDetails = get_hosts_details(Rooms),
×
1374
    [muc_create_room(HostsDetails, A) || A <- Rooms],
×
1375
    ok.
×
1376

1377
muc_create_room(HostsDetails, {_, Host, _} = RoomTuple) ->
1378
    {_Host, ServerHost, DefRoomOpts} = get_host_details(Host, HostsDetails),
×
1379
    muc_create_room(ServerHost, RoomTuple, DefRoomOpts).
×
1380

1381
get_hosts_details(Rooms) ->
1382
    Hosts = lists_uniq([Host || {_, Host, _} <- Rooms]),
×
1383
    lists:map(fun(H) ->
×
1384
                      SH = get_room_serverhost(H),
×
1385
                      {H, SH, mod_muc_opt:default_room_options(SH)}
×
1386
              end, Hosts).
1387

1388
-ifdef(OTP_BELOW_25).
1389
lists_uniq(List) ->
1390
    lists:usort(List).
1391
-else.
1392
lists_uniq(List) ->
1393
    lists:uniq(List).
×
1394
-endif.
1395

1396
get_host_details(Host, ServerHostsDetails) ->
1397
    lists:keyfind(Host, 1, ServerHostsDetails).
×
1398

1399
%%---------------------------------
1400
%% List/Delete Unused/Empty Rooms
1401
%%---------------------------------
1402

1403
%%---------------
1404
%% Control
1405

1406
rooms_unused_list(Service, Days) ->
1407
    rooms_report(unused, list, Service, Days).
×
1408
rooms_unused_destroy(Service, Days) ->
1409
    rooms_report(unused, destroy, Service, Days).
×
1410

1411
rooms_empty_list(Service) ->
1412
    rooms_report(empty, list, Service, 0).
×
1413
rooms_empty_destroy(Service) ->
1414
    rooms_report(empty, destroy, Service, 0).
×
1415

1416
rooms_empty_destroy_restuple(Service) ->
1417
    DestroyedRooms = rooms_report(empty, destroy, Service, 0),
×
1418
    NumberBin = integer_to_binary(length(DestroyedRooms)),
×
1419
    {ok, <<"Destroyed rooms: ", NumberBin/binary>>}.
×
1420

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

1426
muc_unused(Method, Action, Service, Last_allowed) ->
1427
    %% Get all required info about all existing rooms
1428
    Rooms_all = get_all_rooms(Service, erlang:system_time(microsecond) - Last_allowed*24*60*60*1000),
×
1429

1430
    %% Decide which ones pass the requirements
1431
    Rooms_pass = decide_rooms(Method, Rooms_all, Last_allowed),
×
1432

1433
    Num_rooms_all = length(Rooms_all),
×
1434
    Num_rooms_pass = length(Rooms_pass),
×
1435

1436
    %% Perform the desired action for matching rooms
1437
    act_on_rooms(Method, Action, Rooms_pass),
×
1438

1439
    {Num_rooms_all, Num_rooms_pass, Rooms_pass}.
×
1440

1441
%%---------------
1442
%% Get info
1443

1444
get_online_rooms(ServiceArg) ->
1445
    Hosts = find_services(ServiceArg),
×
1446
    lists:flatmap(
×
1447
      fun(Host) ->
1448
          ServerHost = get_room_serverhost(Host),
×
1449
          [{RoomName, RoomHost, ServerHost, Pid}
×
1450
           || {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
×
1451
      end, Hosts).
1452

1453
get_all_rooms(ServiceArg, Timestamp) ->
1454
    Hosts = find_services(ServiceArg),
×
1455
    lists:flatmap(
×
1456
      fun(Host) ->
1457
              get_all_rooms2(Host, Timestamp)
×
1458
      end, Hosts).
1459

1460
get_all_rooms2(Host, Timestamp) ->
1461
    ServerHost = ejabberd_router:host_of_route(Host),
×
1462
    OnlineRooms = get_online_rooms(Host),
×
1463
    OnlineMap = lists:foldl(
×
1464
        fun({Room, _, _, _}, Map) ->
1465
            Map#{Room => 1}
×
1466
        end, #{}, OnlineRooms),
1467

1468
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
×
1469
    DbRooms =
×
1470
    case {erlang:function_exported(Mod, get_rooms_without_subscribers, 2),
1471
          erlang:function_exported(Mod, get_hibernated_rooms_older_than, 3)} of
1472
        {_, true} ->
1473
            Mod:get_hibernated_rooms_older_than(ServerHost, Host, Timestamp);
×
1474
        {true, _} ->
1475
            Mod:get_rooms_without_subscribers(ServerHost, Host);
×
1476
        _ ->
1477
            Mod:get_rooms(ServerHost, Host)
×
1478
    end,
1479
    StoredRooms = lists:filtermap(
×
1480
        fun(#muc_room{name_host = {Room, _}, opts = Opts}) ->
1481
            case maps:is_key(Room, OnlineMap) of
×
1482
                true ->
1483
                    false;
×
1484
                _ ->
1485
                    {true, {Room, Host, ServerHost, Opts}}
×
1486
            end
1487
        end, DbRooms),
1488
    OnlineRooms ++ StoredRooms.
×
1489

1490
get_room_config(Room_pid) ->
1491
    {ok, R} = mod_muc_room:get_config(Room_pid),
×
1492
    R.
×
1493

1494
get_room_state(Room_pid) ->
1495
    {ok, R} = mod_muc_room:get_state(Room_pid),
×
1496
    R.
×
1497

1498
%%---------------
1499
%% Decide
1500

1501
decide_rooms(Method, Rooms, Last_allowed) ->
1502
    Decide = fun(R) -> decide_room(Method, R, Last_allowed) end,
×
1503
    lists:filter(Decide, Rooms).
×
1504

1505
decide_room(unused, {_Room_name, _Host, ServerHost, Room_pid}, Last_allowed) ->
1506
    NodeStartTime = erlang:system_time(microsecond) -
×
1507
                    1000000*(erlang:monotonic_time(second)-ejabberd_config:get_node_start()),
1508
    OnlyHibernated = case mod_muc_opt:hibernation_timeout(ServerHost) of
×
1509
        Value when Value < Last_allowed*24*60*60*1000 ->
1510
            true;
×
1511
        _ ->
1512
            false
×
1513
        end,
1514
    {Just_created, Num_users} =
×
1515
    case Room_pid of
1516
        Pid when is_pid(Pid) andalso OnlyHibernated ->
1517
            {erlang:system_time(microsecond), 0};
×
1518
        Pid when is_pid(Pid) ->
1519
            case mod_muc_room:get_state(Room_pid) of
×
1520
                {ok, #state{just_created = JC, users = U}} ->
1521
                    {JC, maps:size(U)};
×
1522
                _ ->
1523
                    {erlang:system_time(microsecond), 0}
×
1524
            end;
1525
        Opts ->
1526
            case lists:keyfind(hibernation_time, 1, Opts) of
×
1527
                false ->
1528
                    {NodeStartTime, 0};
×
1529
                {_, undefined} ->
1530
                    {NodeStartTime, 0};
×
1531
                {_, T} ->
1532
                    {T, 0}
×
1533
            end
1534
    end,
1535
    Last = case Just_created of
×
1536
               true ->
1537
                   0;
×
1538
               _ ->
1539
                   (erlang:system_time(microsecond)
1540
                    - Just_created) div 1000000
×
1541
           end,
1542
    case {Num_users, seconds_to_days(Last)} of
×
1543
        {0, Last_days} when (Last_days >= Last_allowed) ->
1544
            true;
×
1545
        _ ->
1546
            false
×
1547
    end;
1548
decide_room(empty, {Room_name, Host, ServerHost, Room_pid}, _Last_allowed) ->
1549
    case gen_mod:is_loaded(ServerHost, mod_mam) of
×
1550
        true ->
1551
            Room_options = case Room_pid of
×
1552
                               _ when is_pid(Room_pid) ->
1553
                                   get_room_options(Room_pid);
×
1554
                               Opts ->
1555
                                   Opts
×
1556
                           end,
1557
            case lists:keyfind(<<"mam">>, 1, Room_options) of
×
1558
                {<<"mam">>, <<"true">>} ->
1559
                    mod_mam:is_empty_for_room(ServerHost, Room_name, Host);
×
1560
                _ ->
1561
                    false
×
1562
            end;
1563
        _ ->
1564
            false
×
1565
    end.
1566

1567
seconds_to_days(S) ->
1568
    S div (60*60*24).
×
1569

1570
%%---------------
1571
%% Act
1572

1573
act_on_rooms(Method, Action, Rooms) ->
1574
    Delete = fun(Room) ->
×
1575
                     act_on_room(Method, Action, Room)
×
1576
             end,
1577
    lists:foreach(Delete, Rooms).
×
1578

1579
act_on_room(Method, destroy, {N, H, _SH, Pid}) ->
1580
    Message = iolist_to_binary(io_lib:format(
×
1581
        <<"Room destroyed by rooms_~s_destroy.">>, [Method])),
1582
    case Pid of
×
1583
        V when is_pid(V) ->
1584
            mod_muc_room:destroy(Pid, Message);
×
1585
        _ ->
1586
            case get_room_pid(N, H) of
×
1587
                Pid2 when is_pid(Pid2) ->
1588
                    mod_muc_room:destroy(Pid2, Message);
×
1589
                _ ->
1590
                    ok
×
1591
            end
1592
    end;
1593
act_on_room(_Method, list, _) ->
1594
    ok.
×
1595

1596

1597
%%----------------------------
1598
%% Change Room Option
1599
%%----------------------------
1600

1601
get_room_occupants(Room, Host) ->
1602
    case get_room_pid_validate(Room, Host, <<"service">>) of
×
1603
        {Pid, _, _} when is_pid(Pid) -> get_room_occupants(Pid);
×
1604
        _ -> throw({error, room_not_found})
×
1605
    end.
1606

1607
get_room_occupants(Pid) ->
1608
    S = get_room_state(Pid),
×
1609
    lists:map(
×
1610
      fun({_LJID, Info}) ->
1611
              {jid:encode(Info#user.jid),
×
1612
               Info#user.nick,
1613
               atom_to_list(Info#user.role)}
1614
      end,
1615
      maps:to_list(S#state.users)).
1616

1617
get_room_occupants_number(Room, Host) ->
1618
    case get_room_pid_validate(Room, Host, <<"service">>) of
×
1619
        {Pid, _, _} when is_pid(Pid)->
1620
            {ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid),
×
1621
            N;
×
1622
        _ ->
1623
            throw({error, room_not_found})
×
1624
    end.
1625

1626
%%----------------------------
1627
%% Send Direct Invitation
1628
%%----------------------------
1629
%% http://xmpp.org/extensions/xep-0249.html
1630

1631
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) when is_binary(UsersString) ->
1632
    UsersStrings = binary:split(UsersString, <<":">>, [global]),
×
1633
    send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings);
×
1634
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings) ->
1635
    case jid:make(RoomName, RoomService) of
×
1636
        error ->
1637
            throw({error, "Invalid 'roomname' or 'service'"});
×
1638
        RoomJid ->
1639
            XmlEl = build_invitation(Password, Reason, RoomJid),
×
1640
            Users = get_users_to_invite(RoomJid, UsersStrings),
×
1641
            [send_direct_invitation(RoomJid, UserJid, XmlEl)
×
1642
             || UserJid <- Users],
×
1643
            ok
×
1644
    end.
1645

1646
get_users_to_invite(RoomJid, UsersStrings) ->
1647
    OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
×
1648
                                         RoomJid#jid.lserver),
1649
    OccupantsJids = try [jid:decode(JidString)
×
1650
                         || {JidString, _Nick, _} <- OccupantsTuples]
×
1651
                    catch _:{bad_jid, _} -> throw({error, "Malformed JID of invited user"})
×
1652
                    end,
1653
    lists:filtermap(
×
1654
      fun(UserString) ->
1655
              UserJid = jid:decode(UserString),
×
1656
              Val = lists:all(fun(OccupantJid) ->
×
1657
                                      UserJid#jid.luser /= OccupantJid#jid.luser
×
1658
                                          orelse UserJid#jid.lserver /= OccupantJid#jid.lserver
×
1659
                              end,
1660
                              OccupantsJids),
1661
              case {UserJid#jid.luser, Val} of
×
1662
                  {<<>>, _} -> false;
×
1663
                  {_, true} -> {true, UserJid};
×
1664
                  _ -> false
×
1665
              end
1666
      end,
1667
      UsersStrings).
1668

1669
build_invitation(Password, Reason, RoomJid) ->
1670
    Invite = #x_conference{jid = RoomJid,
×
1671
                           password = case Password of
1672
                                          <<"none">> -> <<>>;
×
1673
                                          _ -> Password
×
1674
                                      end,
1675
                           reason = case Reason of
1676
                                        <<"none">> -> <<>>;
×
1677
                                        _ -> Reason
×
1678
                                    end},
1679
    #message{sub_els = [Invite]}.
×
1680

1681
send_direct_invitation(FromJid, UserJid, Msg) ->
1682
    ejabberd_router:route(xmpp:set_from_to(Msg, FromJid, UserJid)).
×
1683

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

1688
-spec change_room_option(Name::binary(), Service::binary(), Option::binary(),
1689
                         Value::atom() | integer() | string()) -> ok | mod_muc_log_not_enabled.
1690
%% @doc Change an option in an existing room.
1691
%% Requires the name of the room, the MUC service where it exists,
1692
%% the option to change (for example title or max_users),
1693
%% and the value to assign to the new option.
1694
%% For example:
1695
%% `change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)'
1696
change_room_option(Name, Service, OptionString, ValueString) ->
1697
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
1698
        {room_not_found, _, _} ->
1699
            throw({error, "Room not found"});
×
1700
        {Pid, _, _} ->
1701
            {Option, Value} = format_room_option(OptionString, ValueString),
×
1702
            change_room_option(Pid, Option, Value)
×
1703
    end.
1704

1705
change_room_option(Pid, Option, Value) ->
1706
    case {Option,
×
1707
          gen_mod:is_loaded((get_room_state(Pid))#state.server_host, mod_muc_log)} of
1708
        {logging, false} ->
1709
            mod_muc_log_not_enabled;
×
1710
        _ ->
1711
            Config = get_room_config(Pid),
×
1712
            Config2 = change_option(Option, Value, Config),
×
1713
            {ok, _} = mod_muc_room:set_config(Pid, Config2),
×
1714
            ok
×
1715
    end.
1716

1717
format_room_option(OptionString, ValueString) ->
1718
    Option = misc:binary_to_atom(OptionString),
×
1719
    Value = case Option of
×
1720
                title -> ValueString;
×
1721
                description -> ValueString;
×
1722
                password -> ValueString;
×
1723
                subject ->ValueString;
×
1724
                subject_author ->ValueString;
×
1725
                max_users -> try_convert_integer(Option, ValueString);
×
1726
                voice_request_min_interval -> try_convert_integer(Option, ValueString);
×
1727
                vcard -> ValueString;
×
1728
                vcard_xupdate when ValueString /= <<"undefined">>,
1729
                                   ValueString /= <<"external">> ->
1730
                    ValueString;
×
1731
                lang -> ValueString;
×
1732
                pubsub -> ValueString;
×
1733
                affiliations ->
1734
                    [parse_affiliation_string(Opt) || Opt <- str:tokens(ValueString, <<",">>)];
×
1735
                subscribers ->
1736
                    [parse_subscription_string(Opt) || Opt <- str:tokens(ValueString, <<",">>)];
×
1737
                allow_private_messages_from_visitors when
1738
                      (ValueString == <<"anyone">>) or
1739
                      (ValueString == <<"moderators">>) or
1740
                      (ValueString == <<"nobody">>) -> binary_to_existing_atom(ValueString);
×
1741
                allowpm when
1742
                      (ValueString == <<"anyone">>) or
1743
                      (ValueString == <<"participants">>) or
1744
                      (ValueString == <<"moderators">>) or
1745
                      (ValueString == <<"none">>) -> binary_to_existing_atom(ValueString);
×
1746
                presence_broadcast when
1747
                      (ValueString == <<"participant">>) or
1748
                      (ValueString == <<"moderator">>) or
1749
                      (ValueString == <<"visitor">>) -> binary_to_existing_atom(ValueString);
×
1750
                _ when ValueString == <<"true">> -> true;
×
1751
                _ when ValueString == <<"false">> -> false;
×
1752
                _ -> throw_error(Option, ValueString)
×
1753
            end,
1754
    {Option, Value}.
×
1755

1756
try_convert_integer(Option, ValueString) ->
1757
    try binary_to_integer(ValueString) of
×
1758
        I when is_integer(I) -> I
×
1759
    catch _:badarg ->
1760
        throw_error(Option, ValueString)
×
1761
    end.
1762

1763
throw_error(O, V) ->
1764
    throw({error, "Invalid value for that option", O, V}).
×
1765

1766
parse_affiliation_string(String) ->
1767
    {Type, JidS} = case String of
×
1768
                       <<"owner:", Jid/binary>> -> {owner, Jid};
×
1769
                       <<"admin:", Jid/binary>> -> {admin, Jid};
×
1770
                       <<"member:", Jid/binary>> -> {member, Jid};
×
1771
                       <<"outcast:", Jid/binary>> -> {outcast, Jid};
×
1772
                       _ -> throw({error, "Invalid 'affiliation'"})
×
1773
                   end,
1774
    try jid:decode(JidS) of
×
1775
        #jid{luser = U, lserver = S, lresource = R} ->
1776
            {{U, S, R}, {Type, <<>>}}
×
1777
    catch _:{bad_jid, _} ->
1778
        throw({error, "Malformed JID in affiliation"})
×
1779
    end.
1780

1781
parse_subscription_string(String) ->
1782
    case str:tokens(String, <<":">>) of
×
1783
        [_] ->
1784
            throw({error, "Invalid 'subscribers' - missing nick"});
×
1785
        [_, _] ->
1786
            throw({error, "Invalid 'subscribers' - missing nodes"});
×
1787
        [JidS, Nick | Nodes] ->
1788
            Nodes2 = parse_nodes(Nodes, []),
×
1789
            try jid:decode(JidS) of
×
1790
                Jid ->
1791
                    {Jid, Nick, Nodes2}
×
1792
            catch _:{bad_jid, _} ->
1793
                throw({error, "Malformed JID in 'subscribers'"})
×
1794
            end
1795
    end.
1796

1797
parse_nodes([], Acc) ->
1798
    Acc;
×
1799
parse_nodes([<<"presence">> | Rest], Acc) ->
1800
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PRESENCE | Acc]);
×
1801
parse_nodes([<<"messages">> | Rest], Acc) ->
1802
    parse_nodes(Rest, [?NS_MUCSUB_NODES_MESSAGES | Acc]);
×
1803
parse_nodes([<<"participants">> | Rest], Acc) ->
1804
    parse_nodes(Rest, [?NS_MUCSUB_NODES_PARTICIPANTS | Acc]);
×
1805
parse_nodes([<<"affiliations">> | Rest], Acc) ->
1806
    parse_nodes(Rest, [?NS_MUCSUB_NODES_AFFILIATIONS | Acc]);
×
1807
parse_nodes([<<"subject">> | Rest], Acc) ->
1808
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBJECT | Acc]);
×
1809
parse_nodes([<<"config">> | Rest], Acc) ->
1810
    parse_nodes(Rest, [?NS_MUCSUB_NODES_CONFIG | Acc]);
×
1811
parse_nodes([<<"system">> | Rest], Acc) ->
1812
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SYSTEM | Acc]);
×
1813
parse_nodes([<<"subscribers">> | Rest], Acc) ->
1814
    parse_nodes(Rest, [?NS_MUCSUB_NODES_SUBSCRIBERS | Acc]);
×
1815
parse_nodes(_, _) ->
1816
    throw({error, "Invalid 'subscribers' - unknown node name used"}).
×
1817

1818
-spec get_room_pid_validate(binary(), binary(), binary()) ->
1819
    {pid() | room_not_found, binary(), binary()}.
1820
get_room_pid_validate(Name, Service, ServiceArg) ->
1821
    Name2 = validate_room(Name),
6✔
1822
    {ServerHost, Service2} = validate_muc2(Service, ServiceArg),
6✔
1823
    case mod_muc:unhibernate_room(ServerHost, Service2, Name2) of
6✔
1824
        error ->
1825
            {room_not_found, Name2, Service2};
×
1826
        {ok, Pid} ->
1827
            {Pid, Name2, Service2}
6✔
1828
    end.
1829

1830
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
1831
-spec get_room_pid(binary(), binary()) -> pid() | room_not_found | invalid_service | unknown_service.
1832
get_room_pid(Name, Service) ->
1833
    try get_room_serverhost(Service) of
×
1834
        ServerHost ->
1835
            case mod_muc:unhibernate_room(ServerHost, Service, Name) of
×
1836
                error ->
1837
                    room_not_found;
×
1838
                {ok, Pid} ->
1839
                    Pid
×
1840
            end
1841
    catch
1842
        error:{invalid_domain, _} ->
1843
            invalid_service;
×
1844
        error:{unregistered_route, _} ->
1845
            unknown_service
×
1846
    end.
1847

1848
room_diagnostics(Name, Service) ->
1849
    try get_room_serverhost(Service) of
×
1850
        ServerHost ->
1851
            RMod = gen_mod:ram_db_mod(ServerHost, mod_muc),
×
1852
            case RMod:find_online_room(ServerHost, Name, Service) of
×
1853
                error ->
1854
                    room_hibernated;
×
1855
                {ok, Pid} ->
1856
                    case rpc:pinfo(Pid, [current_stacktrace, message_queue_len, messages]) of
×
1857
                        [{_, R}, {_, QL}, {_, Q}] ->
1858
                            #{stacktrace => R, queue_size => QL, queue => lists:sublist(Q, 10)};
×
1859
                        _ ->
1860
                            unable_to_probe_process
×
1861
                    end
1862
            end
1863
    catch
1864
        error:{invalid_domain, _} ->
1865
            invalid_service;
×
1866
        error:{unregistered_route, _} ->
1867
            unknown_service
×
1868
    end.
1869

1870
%% It is required to put explicitly all the options because
1871
%% the record elements are replaced at compile time.
1872
%% So, this can't be parametrized.
1873
change_option(Option, Value, Config) ->
1874
    case Option of
×
1875
        allow_change_subj -> Config#config{allow_change_subj = Value};
×
1876
        allowpm -> Config#config{allowpm = Value};
×
1877
        allow_private_messages_from_visitors -> Config#config{allow_private_messages_from_visitors = Value};
×
1878
        allow_query_users -> Config#config{allow_query_users = Value};
×
1879
        allow_subscription -> Config#config{allow_subscription = Value};
×
1880
        allow_user_invites -> Config#config{allow_user_invites = Value};
×
1881
        allow_visitor_nickchange -> Config#config{allow_visitor_nickchange = Value};
×
1882
        allow_visitor_status -> Config#config{allow_visitor_status = Value};
×
1883
        allow_voice_requests -> Config#config{allow_voice_requests = Value};
×
1884
        anonymous -> Config#config{anonymous = Value};
×
1885
        captcha_protected -> Config#config{captcha_protected = Value};
×
1886
        description -> Config#config{description = Value};
×
1887
        enable_hats -> Config#config{enable_hats = Value};
×
1888
        lang -> Config#config{lang = Value};
×
1889
        logging -> Config#config{logging = Value};
×
1890
        mam -> Config#config{mam = Value};
×
1891
        max_users -> Config#config{max_users = Value};
×
1892
        members_by_default -> Config#config{members_by_default = Value};
×
1893
        members_only -> Config#config{members_only = Value};
×
1894
        moderated -> Config#config{moderated = Value};
×
1895
        password -> Config#config{password = Value};
×
1896
        password_protected -> Config#config{password_protected = Value};
×
1897
        persistent -> Config#config{persistent = Value};
×
1898
        presence_broadcast -> Config#config{presence_broadcast = Value};
×
1899
        public -> Config#config{public = Value};
×
1900
        public_list -> Config#config{public_list = Value};
×
1901
        pubsub -> Config#config{pubsub = Value};
×
1902
        title -> Config#config{title = Value};
×
1903
        vcard -> Config#config{vcard = Value};
×
1904
        vcard_xupdate -> Config#config{vcard_xupdate = Value};
×
1905
        voice_request_min_interval -> Config#config{voice_request_min_interval = Value}
×
1906
    end.
1907

1908
%%----------------------------
1909
%% Get Room Options
1910
%%----------------------------
1911

1912
get_room_options(Name, Service) ->
1913
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
1914
        {Pid, _, _} when is_pid(Pid) -> get_room_options(Pid);
×
1915
        _ -> []
×
1916
    end.
1917

1918
get_room_options(Pid) ->
1919
    Config = get_room_config(Pid),
×
1920
    get_options(Config).
×
1921

1922
get_options(Config) ->
1923
    Fields = [misc:atom_to_binary(Field) || Field <- record_info(fields, config)],
×
1924
    [config | ValuesRaw] = tuple_to_list(Config),
×
1925
    Values = lists:map(fun(V) when is_atom(V) -> misc:atom_to_binary(V);
×
1926
                          (V) when is_integer(V) -> integer_to_binary(V);
×
1927
                          (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
×
1928
                          (V) -> V end, ValuesRaw),
×
1929
    lists:zip(Fields, Values).
×
1930

1931
%%----------------------------
1932
%% Get Room Affiliations
1933
%%----------------------------
1934

1935
%% @spec(Name::binary(), Service::binary()) ->
1936
%%    [{Username::string(), Domain::string(), Role::string(), Reason::string()}]
1937
%% @doc Get the affiliations of  the room Name@Service.
1938
get_room_affiliations(Name, Service) ->
1939
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
1940
        {Pid, _, _} when is_pid(Pid) ->
1941
            %% Get the PID of the online room, then request its state
1942
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
1943
            Affiliations = maps:to_list(StateData#state.affiliations),
×
1944
            lists:map(
×
1945
              fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
1946
                      {Uname, Domain, Aff, Reason};
×
1947
                 ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
1948
                      {Uname, Domain, Aff, <<>>}
×
1949
              end, Affiliations);
1950
        _ ->
1951
            throw({error, "The room does not exist."})
×
1952
    end.
1953

1954
%% @spec(Name::binary(), Service::binary()) ->
1955
%%    [{JID::string(), Role::string(), Reason::string()}]
1956
%% @doc Get the affiliations of  the room Name@Service.
1957
get_room_affiliations_v3(Name, Service) ->
1958
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
1959
        {Pid, _, _} when is_pid(Pid) ->
1960
            %% Get the PID of the online room, then request its state
1961
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
1962
            Affiliations = maps:to_list(StateData#state.affiliations),
×
1963
            lists:map(
×
1964
              fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
1965
                      Jid = makeencode(Uname, Domain),
×
1966
                      {Jid, Aff, Reason};
×
1967
                 ({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
1968
                      Jid = makeencode(Uname, Domain),
×
1969
                      {Jid, Aff, <<>>}
×
1970
              end, Affiliations);
1971
        _ ->
1972
            throw({error, "The room does not exist."})
×
1973
    end.
1974

1975
get_room_history(Name, Service) ->
1976
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
1977
        {Pid, _, _} when is_pid(Pid) ->
1978
            case mod_muc_room:get_state(Pid) of
×
1979
                {ok, StateData} ->
1980
                    History = p1_queue:to_list((StateData#state.history)#lqueue.queue),
×
1981
                    lists:map(
×
1982
                        fun({_Nick, Packet, _HaveSubject, TimeStamp, _Size}) ->
1983
                            {xmpp_util:encode_timestamp(TimeStamp),
×
1984
                             ejabberd_web_admin:pretty_print_xml(xmpp:encode(Packet))}
1985
                        end, History);
1986
                _ ->
1987
                    throw({error, "Unable to fetch room state."})
×
1988
            end;
1989
        _ ->
1990
            throw({error, "The room does not exist."})
×
1991
    end.
1992

1993
%%----------------------------
1994
%% Get Room Affiliation
1995
%%----------------------------
1996

1997
%% @spec(Name::binary(), Service::binary(), JID::binary()) ->
1998
%%    {Affiliation::string()}
1999
%% @doc Get affiliation of a user in the room Name@Service.
2000

2001
get_room_affiliation(Name, Service, JID) ->
2002
    case get_room_pid_validate(Name, Service, <<"service">>) of
×
2003
        {Pid, _, _} when is_pid(Pid) ->
2004
            %% Get the PID of the online room, then request its state
2005
            {ok, StateData} = mod_muc_room:get_state(Pid),
×
2006
            UserJID = jid:decode(JID),
×
2007
            mod_muc_room:get_affiliation(UserJID, StateData);
×
2008
        _ ->
2009
            throw({error, "The room does not exist."})
×
2010
    end.
2011

2012
%%----------------------------
2013
%% Change Room Affiliation
2014
%%----------------------------
2015

2016
set_room_affiliation(Name, Service, User, Host, AffiliationString) ->
2017
    set_room_affiliation(Name, Service, makeencode(User, Host), AffiliationString).
6✔
2018

2019
%% @spec(Name, Service, JID, AffiliationString) -> ok | {error, Error}
2020
%%       Name = binary()
2021
%%       Service = binary()
2022
%%       JID = binary()
2023
%%       AffiliationString = "outcast" | "none" | "member" | "admin" | "owner"
2024
%% @doc Set the affiliation of JID in the room Name@Service.
2025
%% If the affiliation is 'none', the action is to remove,
2026
%% In any other case the action will be to create the affiliation.
2027
set_room_affiliation(Name, Service, JID, AffiliationString) ->
2028
    Affiliation = case AffiliationString of
6✔
2029
                      <<"outcast">> -> outcast;
×
2030
                      <<"none">> -> none;
×
2031
                      <<"member">> -> member;
6✔
2032
                      <<"admin">> -> admin;
×
2033
                      <<"owner">> -> owner;
×
2034
                      _ ->
2035
                          throw({error, "Invalid affiliation"})
×
2036
                  end,
2037
    case get_room_pid_validate(Name, Service, <<"service">>) of
6✔
2038
        {Pid, _, _} when is_pid(Pid) ->
2039
            %% Get the PID for the online room so we can get the state of the room
2040
            case mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>) of
6✔
2041
                {ok, _} ->
2042
                    ok;
6✔
2043
                {error, notfound} ->
2044
                    throw({error, "Room doesn't exists"});
×
2045
                {error, _} ->
2046
                    throw({error, "Unable to perform change"})
×
2047
            end;
2048
        _ ->
2049
            throw({error, "Room doesn't exists"})
×
2050
    end.
2051

2052
%%%
2053
%%% MUC Subscription
2054
%%%
2055

2056
subscribe_room(Username, Host, Nick, Name, Service, Nodes) ->
2057
    subscribe_room(makeencode(Username, Host), Nick,
×
2058
                   makeencode(Name, Service), Nodes).
2059

2060
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
2061
    throw({error, "Nickname must be set"});
×
2062
subscribe_room(User, Nick, Room, Nodes) when is_binary(Nodes) ->
2063
    NodeList = re:split(Nodes, "\\h*,\\h*"),
×
2064
    subscribe_room(User, Nick, Room, NodeList);
×
2065
subscribe_room(User, Nick, Room, NodeList) ->
2066
    try jid:decode(Room) of
×
2067
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
2068
            try jid:decode(User) of
×
2069
                UserJID1 ->
2070
                    UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
×
2071
                    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2072
                        {Pid, _, _} when is_pid(Pid) ->
2073
                            case mod_muc_room:subscribe(
×
2074
                                   Pid, UserJID, Nick, NodeList) of
2075
                                {ok, SubscribedNodes} ->
2076
                                    SubscribedNodes;
×
2077
                                {error, Reason} ->
2078
                                    throw({error, binary_to_list(Reason)})
×
2079
                            end;
2080
                        _ ->
2081
                            throw({error, "The room does not exist"})
×
2082
                    end
2083
            catch _:{bad_jid, _} ->
2084
                    throw({error, "Malformed user JID"})
×
2085
            end;
2086
        _ ->
2087
            throw({error, "Malformed room JID"})
×
2088
    catch _:{bad_jid, _} ->
2089
            throw({error, "Malformed room JID"})
×
2090
    end.
2091

2092
subscribe_room_many_v3(List, Name, Service, Nodes) ->
2093
    List2 = [{makeencode(User, Host), Nick} || {User, Host, Nick} <- List],
×
2094
    subscribe_room_many(List2, makeencode(Name, Service), Nodes).
×
2095

2096
subscribe_room_many(Users, Room, Nodes) ->
2097
    MaxUsers = mod_muc_admin_opt:subscribe_room_many_max_users(global),
×
2098
    if
×
2099
        length(Users) > MaxUsers ->
2100
            throw({error, "Too many users in subscribe_room_many command"});
×
2101
        true ->
2102
            lists:foreach(
×
2103
              fun({User, Nick}) ->
2104
                      subscribe_room(User, Nick, Room, Nodes)
×
2105
              end, Users)
2106
    end.
2107

2108
unsubscribe_room(User, Host, Name, Service) ->
2109
    unsubscribe_room(makeencode(User, Host),
×
2110
                     makeencode(Name, Service)).
2111

2112
unsubscribe_room(User, Room) ->
2113
    try jid:decode(Room) of
×
2114
        #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
2115
            try jid:decode(User) of
×
2116
                UserJID ->
2117
                    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2118
                        {Pid, _, _} when is_pid(Pid) ->
2119
                            case mod_muc_room:unsubscribe(Pid, UserJID) of
×
2120
                                ok ->
2121
                                    ok;
×
2122
                                {error, Reason} ->
2123
                                    throw({error, binary_to_list(Reason)})
×
2124
                            end;
2125
                        _ ->
2126
                            throw({error, "The room does not exist"})
×
2127
                    end
2128
            catch _:{bad_jid, _} ->
2129
                    throw({error, "Malformed user JID"})
×
2130
            end;
2131
        _ ->
2132
            throw({error, "Malformed room JID"})
×
2133
    catch _:{bad_jid, _} ->
2134
            throw({error, "Malformed room JID"})
×
2135
    end.
2136

2137
get_subscribers(Name, Host) ->
2138
    case get_room_pid_validate(Name, Host, <<"service">>) of
×
2139
        {Pid, _, _} when is_pid(Pid) ->
2140
            {ok, JIDList} = mod_muc_room:get_subscribers(Pid),
×
2141
            [jid:encode(jid:remove_resource(J)) || J <- JIDList];
×
2142
        _ ->
2143
            throw({error, "The room does not exist"})
×
2144
    end.
2145

2146
%%----------------------------
2147
%% Utils
2148
%%----------------------------
2149

2150
makeencode(User, Host) ->
2151
    jid:encode(jid:make(User, Host)).
6✔
2152

2153
-spec validate_host(Name :: binary(), ArgName::binary()) -> binary().
2154
validate_host(Name, ArgName) ->
2155
    case jid:nameprep(Name) of
×
2156
        error ->
2157
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2158
        Name2 ->
2159
            case lists:member(Name2, ejabberd_option:hosts()) of
×
2160
                false ->
2161
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2162
                _ ->
2163
                    Name2
×
2164
            end
2165
    end.
2166

2167
-spec validate_user(Name :: binary(), ArgName::binary()) -> binary().
2168
validate_user(Name, ArgName) ->
2169
    case jid:nodeprep(Name) of
×
2170
        error ->
2171
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2172
        Name2 ->
2173
            Name2
×
2174
    end.
2175

2176
-spec validate_muc(Name :: binary(), ArgName::binary()) -> binary().
2177
validate_muc(Name, ArgName) ->
2178
    case jid:nameprep(Name) of
×
2179
        error ->
2180
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2181
        Name2 ->
2182
            try get_room_serverhost(Name2) of
×
2183
                _ -> Name2
×
2184
            catch
2185
                error:{invalid_domain, _} ->
2186
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2187
                error:{unregistered_route, _} ->
2188
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
×
2189
            end
2190
    end.
2191

2192
-spec validate_muc2(Name :: binary(), ArgName::binary()) -> {binary(), binary()}.
2193
validate_muc2(Name, ArgName) ->
2194
    case jid:nameprep(Name) of
6✔
2195
        error ->
2196
            throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
×
2197
        Name2 ->
2198
            try get_room_serverhost(Name2) of
6✔
2199
                Host -> {Host, Name2}
6✔
2200
            catch
2201
                error:{invalid_domain, _} ->
2202
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>});
×
2203
                error:{unregistered_route, _} ->
2204
                    throw({error, <<"Unknown host passed in '",ArgName/binary,"'">>})
×
2205
            end
2206
    end.
2207

2208
-spec validate_room(Name :: binary()) -> binary().
2209
validate_room(Name) ->
2210
    case jid:nodeprep(Name) of
6✔
2211
        error ->
2212
            throw({error, <<"Invalid value of room name">>});
×
2213
        Name2 ->
2214
            Name2
6✔
2215
    end.
2216

2217
find_service(global) ->
2218
    global;
×
2219
find_service(ServerHost) ->
2220
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
×
2221

2222
find_services_validate(Global, _Name) when Global == global;
2223
    Global == <<"global">> ->
2224
    find_services(Global);
×
2225
find_services_validate(Service, Name) ->
2226
    case validate_muc(Service, Name) of
×
2227
        Service2 -> find_services(Service2)
×
2228
    end.
2229

2230
find_services(Global) when Global == global;
2231
                        Global == <<"global">> ->
2232
    lists:flatmap(
×
2233
      fun(ServerHost) ->
2234
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2235
                  true ->
2236
                      [find_service(ServerHost)];
×
2237
                  false ->
2238
                      []
×
2239
              end
2240
      end, ejabberd_option:hosts());
2241
find_services(Service) when is_binary(Service) ->
2242
    [Service].
×
2243

2244
get_room_serverhost(Service) when is_binary(Service) ->
2245
  ejabberd_router:host_of_route(Service).
6✔
2246

2247
find_host(ServerHost) ->
2248
    hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
21✔
2249

2250
find_hosts(Global) when Global == global;
2251
                        Global == <<"global">> ->
2252
    lists:flatmap(
×
2253
      fun(ServerHost) ->
2254
              case gen_mod:is_loaded(ServerHost, mod_muc) of
×
2255
                  true ->
2256
                      [find_host(ServerHost)];
×
2257
                  false ->
2258
                      []
×
2259
              end
2260
      end, ejabberd_option:hosts());
2261
find_hosts(ServerHost) ->
2262
    case gen_mod:is_loaded(ServerHost, mod_muc) of
21✔
2263
        true ->
2264
            [find_host(ServerHost)];
21✔
2265
        false ->
2266
            []
×
2267
    end.
2268

2269
mod_opt_type(subscribe_room_many_max_users) ->
2270
    econf:int().
9✔
2271

2272
mod_options(_) ->
2273
    [{subscribe_room_many_max_users, 50}].
9✔
2274

2275
mod_doc() ->
2276
    #{desc =>
×
2277
          [?T("This module provides commands to administer local MUC "
2278
              "services and their MUC rooms. It also provides simple "
2279
              "WebAdmin pages to view the existing rooms."), "",
2280
           ?T("This module depends on _`mod_muc`_.")],
2281
    opts =>
2282
          [{subscribe_room_many_max_users,
2283
            #{value => ?T("Number"),
2284
              note => "added in 22.05",
2285
              desc =>
2286
                  ?T("How many users can be subscribed to a room at once using "
2287
                     "the _`subscribe_room_many`_ API. "
2288
                     "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