MUC
This is general documentation for developers wanting to interface with Prosody’s MUC module.
Loading modules
It is recommended that MUC-focused modules are loaded directly onto the appropriate host by the user. This means they cannot go in the global modules_enabled list, because those modules get loaded for all VirtualHosts, but not Components.
The correct config would look like:
"rooms.example.com" "muc"
Component = { "my_muc_module", "my_other_muc_module", "super_muc_module" } modules_enabled
Hooking/filtering messages
This is easy, as MUC messages fire the standard stanza events. As per XEP-0045 room messages are sent to the room’s bare JID, so they trigger “message/bare”. Private messages between occupants go to full JIDs, and you can get those with “message/full”. Other stanza types are allowed too.
Accessing room data
mod_muc has a function that returns room objects given a room JID, accessible this way:
local mod_muc = module:depends("muc");
local get_room_from_jid = mod_muc.get_room_from_jid;
Your module will automatically be reloaded or unloaded if mod_muc is.
The get_room_from_jid()
function allows retrieving rooms
by each room’s bare JID, and returns the room object.
local myroom = get_room("myroom@"..module.host);
Config forms
You can add an item to the room’s configuration form by hooking the “muc-config-form” event, which receives a util.dataforms object. When (and if) the form is submitted by the user, the “muc-config-submitted” event is fired, which receives the values extracted from the submitted form.
local st = require "util.stanza";
function handle_form(event)
-- Insert a new field into the form, a simple checkbox
table.insert(event.form, {
= "test";
name type = "boolean";
= "Does it work?";
label = true; -- Default/current value
value });
end
:hook("muc-config-form", handle_form);
module
function handle_submit(event)
local msg = st.message({type='groupchat', from=event.room.jid})
:tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up();
if event.fields.test == true then
:tag("body"):text("It works!"):up();
msgelse
:tag("body"):text("It doesn't work :("):up();
msgend
.room:broadcast_message(msg, false);
eventend
:hook("muc-config-submitted", handle_submit); module
If the config has changed, you must let mod_muc know, so that it can
notify the user according to XEP-0045. Simply set
event.changed = true
for this.
Room API Methods
Chat rooms have a lot of methods. There are a lot of them.
Life cycle
Rooms are created with a create_room()
function that
takes the JID of the room and optionally a configuration table.
local my_room = mod_muc.create_room("talk@muc.example.com", { hidden = false });
Normally this is followed by the creator of the room joining and configuring it, followed by other people joining, exchanging messages, leaving etc, and finally the room may be destroyed.
:destroy() my_room
Configuration
Rooms have getters and setters for all sorts of configuration settings.
Most follow the pattern of
my_room:get_<property>()
to retrieve the current
value and my_room:set_<property>(new_value)
to set a
new value.
These methods generally take care of notifying occupants about changed values or applying other effects.
Name
A room should have a name! It’s a string
shown in service discovery and in the title bar
of many clients.
:get_name() --> the current name
my_room:set_name("The new name") --> boolean indicating success my_room
Description
A string
meant to be a longer public
description of the room for use in service discovery.
:get_description() --> the current value
my_room:set_description("This is the place.") --> boolean indicating success my_room
Subject
A string
meant to be a longer private
description of the current room discussion topic. Sent to occupants on
change and when they join.
:get_subject() --> the current value
my_room:set_subject("Let's discuss the thing") --> boolean indicating success my_room
Change subject
boolean
setting controlling whether regular participants
of the chat may change the subject, or whether it is restricted to
moderators.
:get_changesubject() --> the current value
my_room:set_changesubject(true) --> boolean indicating success my_room
Language
A string
meant to contain a language tag
indicating the main or preferred language in the room.
if my_room:get_language() == "sv" then
:set_subject("Hej!")
my_roomend
:set_language("en") --> boolean indicating success my_room
Moderated
boolean
setting that determines the role new participants join as, visitor
when true
or participant
when
false
. This indirectly controls whether they are allowed to
send messages immediately after joining. No effect on current
participants.
:get_moderated() --> the current value
my_room:set_moderated(false) --> boolean indicating success my_room
Members only
A boolean
controlling whether users without affiliation are allowed to join the room. If
changed from false
to true
, any unaffiliated
occupants are removed from the room.
:get_members_only() --> the current value
my_room:set_members_only(false) --> boolean indicating success my_room
Password
Rooms can require a string
password to enter. Rarely
used since affiliations are more robust.
if password ~= my_room:get_password() then
return false, "access denied"
end
:set_password("correct horse battery staple") --> boolean my_room
Persistent
boolean
that determines whether the room stays around
after everyone leaves it.
:get_persistent() --> the current value
my_room:set_persistent(true) --> boolean indicating success my_room
Allow member invites
Users can send mediated invites trough the room to invite new participants. Because it goes trough the room, it can conveniently grant member affiliations at the same time.
This boolean
setting controls whether regular members
are allowed to do this (if true
).
:get_allow_member_invites() --> current boolean value
my_room:set_allow_member_invites(true or false) --> boolean indicating success my_room
Note that users can always send Direct Invitations that bypass the MUC.
History length
The room keeps some recent
history cached that can be sent to those who join so they have some
context. This setting manages an integer
for how many
messages to keep in memory.
:historylength() --> current integer value
my_room:historylength(20) --> boolean indicating success my_room
Presence broadcast
:get_presence_broadcast() --> table like below
my_room:set_presence_broadcast({
my_room= false;
visitor = true;
participant = true;
moderator })
Occupants
Anyone who joins a room becomes an occupant.
if my_room:has_occupant() then
print("Look at all my friends!")
for occupant in my_room:each_occupant() do
print(occupant.nick)
end
else
print("Oh no, where is everybody?")
end
When dealing with occupants, there will be two kinds of JIDs to deal
with, real JIDs and occupant JIDs. Real JIDs are the full JIDs that the
user joined from, and the occupant JID is the in-room JID composed of
the JID of the room itself and the occupants nickname, so it has the
form room@muc.host/Nickname
.
When a message arrives at the MUC, the real JID should be in the
from
attribute, and from there you can find the occupant
JID:
local who = my_room:get_occupant_jid(stanza.attr.from);
From the occupant JID you can retrieve the occupant object:
local they = my_room:get_occupant_by_nick(who);
You can also retrieve the occupant object directly from the real JID:
local them = my_room:get_occupant_by_real_jid(stanza.attr.from);
Occupant objects are table
values looking like this:
local occupant = {
= "user@example.net";
bare_jid = "room@muc.host/Nickname";
nick = {
sessions -- Multiple sessions may use the same Occupant
-- Their latest presence stanza is kept here
["user@example.net/desktop"] = st.presence();
["user@example.net/mobile"] = st.presence();
};
= "participant";
role = "user@example.net/desktop"; -- the primary session
jid }
One occupant may contain many sessions, generally one for each of the users devices. One session is selected as primary and used as the one true real JID of the occupant, where one is needed.
Occupants are created via the :new_occupant()
and
:save_occupant()
methods:
local new_someone = my_room:new_occupant("user@example.net",
"room@muc.host/Someone");
:set_session("user@example.net/desktop", st.presence());
new_someone:save_occupant(new_someone); my_room
Occupant objects have a few methods to manage sessions:
:set_session(realJID, Stanza, make_primary)
adds a new session from a real JID and a<presence>
stanza, optionally setting it as primary by passingtrue
as 3rd argument.:remove_session(realJID)
to remove a session based on its real JID.- Iterate over sessions with
:each_session()
:choose_new_primary()
decides on a new primary session and returns its real JID.
Affiliations
The creator and first to join a room becomes its first ‘owner’, a persistent kind of membership that grants permissions to do most actions with the room, including granting membership.
Affiliations are:
owner
- Can change most details of the room
admin
- Moderators
member
- Regular members
outcast
- Banned, can’t join
none
- For anyone without a real affiliation
Roles
Occupants are given roles, which grant permission to do various things with the room, from sending messages to changing the roles of other occupants.
The role given to someone joining is based on their affiliation, if any, or room configuration.
Routing
Since the primary job of a MUC room is to broadcast incoming messages to everyone in it, it has a few methods to do precisely this, along with other routing methods.
Incoming
Messages and other stanzas are hooked by mod_muc and dispatched to various room methods that process them and further dispatches to other methods.
Outgoing
When the room needs to send something to a single occupant or broadcast it to all participants, a few different methods are used.