2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- Copyright (C) 2014 Daurnimator
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
10 local select
= select
;
13 local setmetatable
= setmetatable
;
15 local dataform
= require
"util.dataforms";
16 local iterators
= require
"util.iterators";
17 local jid_split
= require
"util.jid".split
;
18 local jid_bare
= require
"util.jid".bare
;
19 local jid_prep
= require
"util.jid".prep
;
20 local jid_join
= require
"util.jid".join
;
21 local jid_resource
= require
"util.jid".resource
;
22 local resourceprep
= require
"util.encodings".stringprep
.resourceprep
;
23 local st
= require
"util.stanza";
24 local base64
= require
"util.encodings".base64
;
25 local md5
= require
"util.hashes".md5
;
27 local log = module
._log
;
29 local occupant_lib
= module
:require
"muc/occupant"
30 local muc_util
= module
:require
"muc/util";
31 local is_kickable_error
= muc_util
.is_kickable_error
;
32 local valid_roles
, valid_affiliations
= muc_util
.valid_roles
, muc_util
.valid_affiliations
;
35 room_mt
.__index
= room_mt
;
37 function room_mt
:__tostring()
38 return "MUC room ("..self
.jid
..")";
41 function room_mt
.save()
42 -- overriden by mod_muc.lua
45 function room_mt
:get_occupant_jid(real_jid
)
46 return self
._jid_nick
[real_jid
]
49 function room_mt
:get_default_role(affiliation
)
50 local role
= module
:fire_event("muc-get-default-role", {
52 affiliation
= affiliation
;
53 affiliation_rank
= valid_affiliations
[affiliation
or "none"];
55 role
= role
~= "none" and role
or nil; -- coerces `role == false` to `nil`
56 return role
, valid_roles
[role
or "none"];
58 module
:hook("muc-get-default-role", function(event
)
59 if event
.affiliation_rank
>= valid_affiliations
.admin
then
61 elseif event
.affiliation_rank
>= valid_affiliations
.none
then
66 --- Occupant functions
67 function room_mt
:new_occupant(bare_real_jid
, nick
)
68 local occupant
= occupant_lib
.new(bare_real_jid
, nick
);
69 local affiliation
= self
:get_affiliation(bare_real_jid
);
70 occupant
.role
= self
:get_default_role(affiliation
);
74 -- nick is in the form of an in-room JID
75 function room_mt
:get_occupant_by_nick(nick
)
76 local occupant
= self
._occupants
[nick
];
77 if occupant
== nil then return nil end
78 return occupant_lib
.copy(occupant
);
82 local function next_copied_occupant(occupants
, occupant_jid
)
83 local next_occupant_jid
, raw_occupant
= next(occupants
, occupant_jid
);
84 if next_occupant_jid
== nil then return nil end
85 return next_occupant_jid
, occupant_lib
.copy(raw_occupant
);
87 -- FIXME Explain what 'read_only' is supposed to be
88 function room_mt
:each_occupant(read_only
) -- luacheck: ignore 212
89 return next_copied_occupant
, self
._occupants
, nil;
93 function room_mt
:has_occupant()
94 return next(self
._occupants
, nil) ~= nil
97 function room_mt
:get_occupant_by_real_jid(real_jid
)
98 local occupant_jid
= self
:get_occupant_jid(real_jid
);
99 if occupant_jid
== nil then return nil end
100 return self
:get_occupant_by_nick(occupant_jid
);
103 function room_mt
:save_occupant(occupant
)
104 occupant
= occupant_lib
.copy(occupant
); -- So that occupant can be modified more
105 local id
= occupant
.nick
107 -- Need to maintain _jid_nick secondary index
108 local old_occupant
= self
._occupants
[id
];
110 for real_jid
in old_occupant
:each_session() do
111 self
._jid_nick
[real_jid
] = nil;
115 local has_live_session
= false
116 if occupant
.role
~= nil then
117 for real_jid
, presence
in occupant
:each_session() do
118 if presence
.attr
.type == nil then
119 has_live_session
= true
120 self
._jid_nick
[real_jid
] = occupant
.nick
;
123 if not has_live_session
then
124 -- Has no live sessions left; they have left the room.
128 if not has_live_session
then
131 self
._occupants
[id
] = occupant
135 function room_mt
:route_to_occupant(occupant
, stanza
)
136 local to
= stanza
.attr
.to
;
137 for jid
in occupant
:each_session() do
138 stanza
.attr
.to
= jid
;
139 self
:route_stanza(stanza
);
144 -- actor is the attribute table
145 local function add_item(x
, affiliation
, role
, jid
, nick
, actor_nick
, actor_jid
, reason
)
146 x
:tag("item", {affiliation
= affiliation
; role
= role
; jid
= jid
; nick
= nick
;})
147 if actor_nick
or actor_jid
then
148 x
:tag("actor", {nick
= actor_nick
; jid
= actor_jid
;}):up()
151 x
:tag("reason"):text(reason
):up()
157 -- actor is (real) jid
158 function room_mt
:build_item_list(occupant
, x
, is_anonymous
, nick
, actor_nick
, actor_jid
, reason
)
159 local affiliation
= self
:get_affiliation(occupant
.bare_jid
) or "none";
160 local role
= occupant
.role
or "none";
162 add_item(x
, affiliation
, role
, nil, nick
, actor_nick
, actor_jid
, reason
);
164 for real_jid
in occupant
:each_session() do
165 add_item(x
, affiliation
, role
, real_jid
, nick
, actor_nick
, actor_jid
, reason
);
171 function room_mt
:broadcast_message(stanza
)
172 if module
:fire_event("muc-broadcast-message", {room
= self
, stanza
= stanza
}) then
175 self
:broadcast(stanza
);
179 -- Strip delay tags claiming to be from us
180 module
:hook("muc-occupant-groupchat", function (event
)
181 local stanza
= event
.stanza
;
182 local room
= event
.room
;
183 local room_jid
= room
.jid
;
185 stanza
:maptags(function (child
)
186 if child
.name
== "delay" and child
.attr
["xmlns"] == "urn:xmpp:delay" then
187 if child
.attr
["from"] == room_jid
then
191 if child
.name
== "x" and child
.attr
["xmlns"] == "jabber:x:delay" then
192 if child
.attr
["from"] == room_jid
then
200 -- Broadcast a stanza to all occupants in the room.
201 -- optionally checks conditional called with (nick, occupant)
202 function room_mt
:broadcast(stanza
, cond_func
)
203 for nick
, occupant
in self
:each_occupant() do
204 if cond_func
== nil or cond_func(nick
, occupant
) then
205 self
:route_to_occupant(occupant
, stanza
)
210 local function can_see_real_jids(whois
, occupant
)
211 if whois
== "anyone" then
213 elseif whois
== "moderators" then
214 return valid_roles
[occupant
.role
or "none"] >= valid_roles
.moderator
;
218 -- Broadcasts an occupant's presence to the whole room
219 -- Takes the x element that goes into the stanzas
220 function room_mt
:publicise_occupant_status(occupant
, x
, nick
, actor
, reason
)
221 local base_x
= x
.base
or x
;
222 -- Build real jid and (optionally) occupant jid template presences
223 local base_presence
do
224 -- Try to use main jid's presence
225 local pr
= occupant
:get_presence();
226 if pr
and (occupant
.role
~= nil or pr
.attr
.type == "unavailable") then
227 base_presence
= st
.clone(pr
);
228 else -- user is leaving but didn't send a leave presence. make one for them
229 base_presence
= st
.presence
{from
= occupant
.nick
; type = "unavailable";};
233 -- Fire event (before full_p and anon_p are created)
235 room
= self
; stanza
= base_presence
; x
= base_x
;
236 occupant
= occupant
; nick
= nick
; actor
= actor
;
239 module
:fire_event("muc-broadcast-presence", event
);
241 -- Allow muc-broadcast-presence listeners to change things
244 reason
= event
.reason
;
246 local whois
= self
:get_whois();
250 actor_nick
= jid_resource(self
:get_occupant_jid(actor
));
253 local full_p
, full_x
;
254 local function get_full_p()
255 if full_p
== nil then
256 full_x
= st
.clone(x
.full
or base_x
);
257 self
:build_item_list(occupant
, full_x
, false, nick
, actor_nick
, actor
, reason
);
258 full_p
= st
.clone(base_presence
):add_child(full_x
);
260 return full_p
, full_x
;
263 local anon_p
, anon_x
;
264 local function get_anon_p()
265 if anon_p
== nil then
266 anon_x
= st
.clone(x
.anon
or base_x
);
267 self
:build_item_list(occupant
, anon_x
, true, nick
, actor_nick
, nil, reason
);
268 anon_p
= st
.clone(base_presence
):add_child(anon_x
);
270 return anon_p
, anon_x
;
273 local self_p
, self_x
;
275 -- Can always see your own full jids
276 -- But not allowed to see actor's
277 self_x
= st
.clone(x
.self
or base_x
);
278 self
:build_item_list(occupant
, self_x
, false, nick
, actor_nick
, nil, reason
);
279 self_p
= st
.clone(base_presence
):add_child(self_x
);
283 for occupant_nick
, n_occupant
in self
:each_occupant() do
284 if occupant_nick
~= occupant
.nick
then
286 if can_see_real_jids(whois
, n_occupant
) then
288 elseif occupant
.bare_jid
== n_occupant
.bare_jid
then
293 self
:route_to_occupant(n_occupant
, pr
);
297 -- Presences for occupant itself
298 self_x
:tag("status", {code
= "110";}):up();
299 if occupant
.role
== nil then
300 -- They get an unavailable
301 self
:route_to_occupant(occupant
, self_p
);
303 -- use their own presences as templates
304 for full_jid
, pr
in occupant
:each_session() do
306 pr
.attr
.to
= full_jid
;
307 pr
:add_child(self_x
);
308 self
:route_stanza(pr
);
313 function room_mt
:send_occupant_list(to
, filter
)
314 local to_bare
= jid_bare(to
);
315 local is_anonymous
= false;
316 local whois
= self
:get_whois();
317 if whois
~= "anyone" then
318 local affiliation
= self
:get_affiliation(to
);
319 if affiliation
~= "admin" and affiliation
~= "owner" then
320 local occupant
= self
:get_occupant_by_real_jid(to
);
321 if not (occupant
and can_see_real_jids(whois
, occupant
)) then
326 for occupant_jid
, occupant
in self
:each_occupant() do
327 if filter
== nil or filter(occupant_jid
, occupant
) then
328 local x
= st
.stanza("x", {xmlns
='http://jabber.org/protocol/muc#user'});
329 self
:build_item_list(occupant
, x
, is_anonymous
and to_bare
~= occupant
.bare_jid
); -- can always see your own jids
330 local pres
= st
.clone(occupant
:get_presence());
333 self
:route_stanza(pres
);
338 function room_mt
:get_disco_info(stanza
)
339 local node
= stanza
.tags
[1].attr
.node
or "";
340 local reply
= st
.reply(stanza
):tag("query", { xmlns
= "http://jabber.org/protocol/disco#info", node
= node
});
341 local event_name
= "muc-disco#info";
342 local event_data
= { room
= self
, reply
= reply
, stanza
= stanza
};
345 event_name
= event_name
.."/"..node
;
347 event_data
.form
= dataform
.new
{
348 { name
= "FORM_TYPE", type = "hidden", value
= "http://jabber.org/protocol/muc#roominfo" };
350 event_data
.formdata
= {};
352 module
:fire_event(event_name
, event_data
);
353 if event_data
.form
then
354 reply
:add_child(event_data
.form
:form(event_data
.formdata
, "result"));
358 module
:hook("muc-disco#info", function(event
)
359 event
.reply
:tag("feature", {var
= "http://jabber.org/protocol/muc"}):up();
360 event
.reply
:tag("feature", {var
= "http://jabber.org/protocol/muc#stable_id"}):up();
362 module
:hook("muc-disco#info", function(event
)
363 table.insert(event
.form
, { name
= "muc#roominfo_occupants", label
= "Number of occupants" });
364 event
.formdata
["muc#roominfo_occupants"] = tostring(iterators
.count(event
.room
:each_occupant()));
367 function room_mt
:get_disco_items(stanza
) -- luacheck: ignore 212
368 return st
.reply(stanza
):query("http://jabber.org/protocol/disco#items");
371 function room_mt
:handle_kickable(origin
, stanza
) -- luacheck: ignore 212
372 local real_jid
= stanza
.attr
.from
;
373 local occupant
= self
:get_occupant_by_real_jid(real_jid
);
374 if occupant
== nil then return nil; end
375 local type, condition
, text
= stanza
:get_error();
376 local error_message
= "Kicked: "..(condition
and condition
:gsub("%-", " ") or "presence error");
377 if text
and self
:get_whois() == "anyone" then
378 error_message
= error_message
..": "..text
;
380 occupant
:set_session(real_jid
, st
.presence({type="unavailable"})
381 :tag('status'):text(error_message
));
382 local is_last_session
= occupant
.jid
== real_jid
;
383 if is_last_session
then
386 local new_occupant
= self
:save_occupant(occupant
);
387 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
388 if is_last_session
then
389 x
:tag("status", {code
= "333"});
391 self
:publicise_occupant_status(new_occupant
or occupant
, x
);
392 if is_last_session
then
393 module
:fire_event("muc-occupant-left", {room
= self
; nick
= occupant
.nick
; occupant
= occupant
;});
398 -- Give the room creator owner affiliation
399 module
:hook("muc-room-pre-create", function(event
)
400 event
.room
:set_affiliation(true, jid_bare(event
.stanza
.attr
.from
), "owner");
403 -- check if user is banned
404 module
:hook("muc-occupant-pre-join", function(event
)
405 local room
, stanza
= event
.room
, event
.stanza
;
406 local affiliation
= room
:get_affiliation(stanza
.attr
.from
);
407 if affiliation
== "outcast" then
408 local reply
= st
.error_reply(stanza
, "auth", "forbidden"):up();
409 reply
.tags
[1].attr
.code
= "403";
410 event
.origin
.send(reply
:tag("x", {xmlns
= "http://jabber.org/protocol/muc"}));
415 module
:hook("muc-occupant-pre-join", function(event
)
416 local nick
= jid_resource(event
.occupant
.nick
);
417 if not nick
:find("%S") then
418 event
.origin
.send(st
.error_reply(event
.stanza
, "modify", "not-allowed", "Invisible Nicknames are forbidden"));
423 module
:hook("muc-occupant-pre-change", function(event
)
424 if not jid_resource(event
.dest_occupant
.nick
):find("%S") then
425 event
.origin
.send(st
.error_reply(event
.stanza
, "modify", "not-allowed", "Invisible Nicknames are forbidden"));
430 function room_mt
:handle_first_presence(origin
, stanza
)
431 if not stanza
:get_child("x", "http://jabber.org/protocol/muc") then
432 module
:log("debug", "Room creation without <x>, possibly desynced");
434 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found"));
438 local real_jid
= stanza
.attr
.from
;
439 local dest_jid
= stanza
.attr
.to
;
440 local bare_jid
= jid_bare(real_jid
);
441 if module
:fire_event("muc-room-pre-create", {
445 }) then return true; end
446 local is_first_dest_session
= true;
447 local dest_occupant
= self
:new_occupant(bare_jid
, dest_jid
);
449 local orig_nick
= dest_occupant
.nick
;
450 if module
:fire_event("muc-occupant-pre-join", {
454 is_first_session
= is_first_dest_session
;
456 occupant
= dest_occupant
;
457 }) then return true; end
458 local nick_changed
= orig_nick
~= dest_occupant
.nick
;
460 dest_occupant
:set_session(real_jid
, stanza
);
461 local dest_x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
462 dest_x
:tag("status", {code
= "201"}):up();
463 if self
:get_whois() == "anyone" then
464 dest_x
:tag("status", {code
= "100"}):up();
467 dest_x
:tag("status", {code
= "210"}):up();
469 self
:save_occupant(dest_occupant
);
471 self
:publicise_occupant_status(dest_occupant
, dest_x
);
473 module
:fire_event("muc-occupant-joined", {
475 nick
= dest_occupant
.nick
;
476 occupant
= dest_occupant
;
480 module
:fire_event("muc-occupant-session-new", {
482 nick
= dest_occupant
.nick
;
483 occupant
= dest_occupant
;
488 module
:fire_event("muc-room-created", {
490 creator
= dest_occupant
;
497 function room_mt
:handle_normal_presence(origin
, stanza
)
498 local type = stanza
.attr
.type;
499 local real_jid
= stanza
.attr
.from
;
500 local bare_jid
= jid_bare(real_jid
);
501 local orig_occupant
= self
:get_occupant_by_real_jid(real_jid
);
502 local muc_x
= stanza
:get_child("x", "http://jabber.org/protocol/muc");
504 if orig_occupant
== nil and not muc_x
and stanza
.attr
.type == nil then
505 module
:log("debug", "Attempted join without <x>, possibly desynced");
506 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found",
507 "You must join the room before sending presence updates"));
511 local is_first_dest_session
;
513 if type == "unavailable" then
514 if orig_occupant
== nil then return true; end -- Unavailable from someone not in the room
515 -- dest_occupant = nil
516 elseif orig_occupant
and orig_occupant
.nick
== stanza
.attr
.to
then -- Just a presence update
517 log("debug", "presence update for %s from session %s", orig_occupant
.nick
, real_jid
);
518 dest_occupant
= orig_occupant
;
520 local dest_jid
= stanza
.attr
.to
;
521 dest_occupant
= self
:get_occupant_by_nick(dest_jid
);
522 if dest_occupant
== nil then
523 log("debug", "no occupant found for %s; creating new occupant object for %s", dest_jid
, real_jid
);
524 is_first_dest_session
= true;
525 dest_occupant
= self
:new_occupant(bare_jid
, dest_jid
);
527 is_first_dest_session
= false;
530 local is_last_orig_session
;
531 if orig_occupant
~= nil then
532 -- Is there are least 2 sessions?
533 local iter
, ob
, last
= orig_occupant
:each_session();
534 is_last_orig_session
= iter(ob
, iter(ob
, last
)) == nil;
537 local orig_nick
= dest_occupant
and dest_occupant
.nick
;
539 local event
, event_name
= {
543 is_first_session
= is_first_dest_session
;
544 is_last_session
= is_last_orig_session
;
546 if orig_occupant
== nil then
547 event_name
= "muc-occupant-pre-join";
548 event
.occupant
= dest_occupant
;
549 elseif dest_occupant
== nil then
550 event_name
= "muc-occupant-pre-leave";
551 event
.occupant
= orig_occupant
;
553 event_name
= "muc-occupant-pre-change";
554 event
.orig_occupant
= orig_occupant
;
555 event
.dest_occupant
= dest_occupant
;
557 if module
:fire_event(event_name
, event
) then return true; end
559 local nick_changed
= dest_occupant
and orig_nick
~= dest_occupant
.nick
;
561 -- Check for nick conflicts
562 if dest_occupant
~= nil and not is_first_dest_session
563 and bare_jid
~= jid_bare(dest_occupant
.bare_jid
) then
564 -- new nick or has different bare real jid
565 log("debug", "%s couldn't join due to nick conflict: %s", real_jid
, dest_occupant
.nick
);
566 local reply
= st
.error_reply(stanza
, "cancel", "conflict"):up();
567 reply
.tags
[1].attr
.code
= "409";
568 origin
.send(reply
:tag("x", {xmlns
= "http://jabber.org/protocol/muc"}));
572 -- Send presence stanza about original occupant
573 if orig_occupant
~= nil and orig_occupant
~= dest_occupant
then
574 local orig_x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
576 if dest_occupant
== nil then -- Session is leaving
577 log("debug", "session %s is leaving occupant %s", real_jid
, orig_occupant
.nick
);
578 if is_last_orig_session
then
579 orig_occupant
.role
= nil;
581 orig_occupant
:set_session(real_jid
, stanza
);
583 log("debug", "session %s is changing from occupant %s to %s", real_jid
, orig_occupant
.nick
, dest_occupant
.nick
);
584 local generated_unavail
= st
.presence
{from
= orig_occupant
.nick
, to
= real_jid
, type = "unavailable"};
585 orig_occupant
:set_session(real_jid
, generated_unavail
);
586 dest_nick
= jid_resource(dest_occupant
.nick
);
587 if not is_first_dest_session
then -- User is swapping into another pre-existing session
588 log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid
, dest_occupant
.nick
);
589 -- Show the other session leaving
590 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
591 add_item(x
, self
:get_affiliation(bare_jid
), "none");
592 local pr
= st
.presence
{from
= dest_occupant
.nick
, to
= real_jid
, type = "unavailable"}
593 :tag("status"):text("you are joining pre-existing session " .. dest_nick
):up()
595 self
:route_stanza(pr
);
597 if is_first_dest_session
and is_last_orig_session
then -- Normal nick change
598 log("debug", "no sessions in %s left; publicly marking as nick change", orig_occupant
.nick
);
599 orig_x
:tag("status", {code
= "303";}):up();
600 else -- The session itself always needs to see a nick change
601 -- don't want to get our old nick's available presence,
602 -- so remove our session from there, and manually generate an unavailable
603 orig_occupant
:remove_session(real_jid
);
604 log("debug", "generating nick change for %s", real_jid
);
605 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
606 -- COMPAT: clients get confused if they see other items besides their own
607 -- self:build_item_list(orig_occupant, x, false, dest_nick);
608 add_item(x
, self
:get_affiliation(bare_jid
), orig_occupant
.role
, real_jid
, dest_nick
);
609 x
:tag("status", {code
= "303";}):up();
610 x
:tag("status", {code
= "110";}):up();
611 self
:route_stanza(generated_unavail
:add_child(x
));
612 dest_nick
= nil; -- set dest_nick to nil; so general populance doesn't see it for whole orig_occupant
616 self
:save_occupant(orig_occupant
);
617 self
:publicise_occupant_status(orig_occupant
, orig_x
, dest_nick
);
619 if is_last_orig_session
then
620 module
:fire_event("muc-occupant-left", {
622 nick
= orig_occupant
.nick
;
623 occupant
= orig_occupant
;
630 if dest_occupant
~= nil then
631 dest_occupant
:set_session(real_jid
, stanza
);
632 self
:save_occupant(dest_occupant
);
634 if orig_occupant
== nil or muc_x
then
635 -- Send occupant list to newly joined or desynced user
636 self
:send_occupant_list(real_jid
, function(nick
, occupant
) -- luacheck: ignore 212
637 -- Don't include self
638 return occupant
:get_presence(real_jid
) == nil;
641 local dest_x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
642 local self_x
= st
.clone(dest_x
);
643 if orig_occupant
== nil and self
:get_whois() == "anyone" then
644 self_x
:tag("status", {code
= "100"}):up();
647 self_x
:tag("status", {code
="210"}):up();
649 self
:publicise_occupant_status(dest_occupant
, {base
=dest_x
,self
=self_x
});
651 if orig_occupant
~= nil and orig_occupant
~= dest_occupant
and not is_last_orig_session
then
652 -- If user is swapping and wasn't last original session
653 log("debug", "session %s split nicks; showing %s rejoining", real_jid
, orig_occupant
.nick
);
654 -- Show the original nick joining again
655 local pr
= st
.clone(orig_occupant
:get_presence());
656 pr
.attr
.to
= real_jid
;
657 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
658 self
:build_item_list(orig_occupant
, x
, false);
659 -- TODO: new status code to inform client this was the multi-session it left?
661 self
:route_stanza(pr
);
664 if orig_occupant
== nil or muc_x
then
665 if is_first_dest_session
then
666 module
:fire_event("muc-occupant-joined", {
668 nick
= dest_occupant
.nick
;
669 occupant
= dest_occupant
;
674 module
:fire_event("muc-occupant-session-new", {
676 nick
= dest_occupant
.nick
;
677 occupant
= dest_occupant
;
687 function room_mt
:handle_presence_to_occupant(origin
, stanza
)
688 local type = stanza
.attr
.type;
689 if type == "error" then -- error, kick em out!
690 return self
:handle_kickable(origin
, stanza
)
691 elseif type == nil or type == "unavailable" then
692 return self
:handle_normal_presence(origin
, stanza
);
693 elseif type ~= 'result' then -- bad type
694 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
695 origin
.send(st
.error_reply(stanza
, "modify", "bad-request")); -- FIXME correct error?
701 function room_mt
:handle_iq_to_occupant(origin
, stanza
)
702 local from
, to
= stanza
.attr
.from
, stanza
.attr
.to
;
703 local type = stanza
.attr
.type;
704 local id
= stanza
.attr
.id
;
705 local occupant
= self
:get_occupant_by_nick(to
);
706 if (type == "error" or type == "result") then
707 do -- deconstruct_stanza_id
708 if not occupant
then return nil; end
709 local from_jid
, orig_id
, to_jid_hash
= (base64
.decode(id
) or ""):match("^(%Z+)%z(%Z*)%z(.+)$");
710 if not(from
== from_jid
or from
== jid_bare(from_jid
)) then return nil; end
711 local from_occupant_jid
= self
:get_occupant_jid(from_jid
);
712 if from_occupant_jid
== nil then return nil; end
714 for to_jid
in occupant
:each_session() do
715 if md5(to_jid
) == to_jid_hash
then
716 session_jid
= to_jid
;
720 if session_jid
== nil then return nil; end
721 stanza
.attr
.from
, stanza
.attr
.to
, stanza
.attr
.id
= from_occupant_jid
, session_jid
, orig_id
;
723 log("debug", "%s sent private iq stanza to %s (%s)", from
, to
, stanza
.attr
.to
);
724 self
:route_stanza(stanza
);
725 stanza
.attr
.from
, stanza
.attr
.to
, stanza
.attr
.id
= from
, to
, id
;
727 else -- Type is "get" or "set"
728 local current_nick
= self
:get_occupant_jid(from
);
729 if not current_nick
then
730 origin
.send(st
.error_reply(stanza
, "cancel", "not-acceptable"));
733 if not occupant
then -- recipient not in room
734 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Recipient not in room"));
737 -- XEP-0410 MUC Self-Ping #1220
738 if to
== current_nick
and stanza
.attr
.type == "get" and stanza
:get_child("ping", "urn:xmpp:ping") then
739 self
:route_stanza(st
.reply(stanza
));
742 do -- construct_stanza_id
743 stanza
.attr
.id
= base64
.encode(occupant
.jid
.."\0"..stanza
.attr
.id
.."\0"..md5(from
));
745 stanza
.attr
.from
, stanza
.attr
.to
= current_nick
, occupant
.jid
;
746 log("debug", "%s sent private iq stanza to %s (%s)", from
, to
, occupant
.jid
);
747 local iq_ns
= stanza
.tags
[1].attr
.xmlns
;
748 if iq_ns
== 'vcard-temp' or iq_ns
== "http://jabber.org/protocol/pubsub" or iq_ns
== "urn:ietf:params:xml:ns:vcard-4.0" then
749 stanza
.attr
.to
= jid_bare(stanza
.attr
.to
);
751 self
:route_stanza(stanza
);
752 stanza
.attr
.from
, stanza
.attr
.to
, stanza
.attr
.id
= from
, to
, id
;
757 function room_mt
:handle_message_to_occupant(origin
, stanza
)
758 local from
, to
= stanza
.attr
.from
, stanza
.attr
.to
;
759 local current_nick
= self
:get_occupant_jid(from
);
760 local type = stanza
.attr
.type;
761 if not current_nick
then -- not in room
762 if type ~= "error" then
763 origin
.send(st
.error_reply(stanza
, "cancel", "not-acceptable"));
767 if type == "groupchat" then -- groupchat messages not allowed in PM
768 origin
.send(st
.error_reply(stanza
, "modify", "bad-request"));
770 elseif type == "error" and is_kickable_error(stanza
) then
771 log("debug", "%s kicked from %s for sending an error message", current_nick
, self
.jid
);
772 return self
:handle_kickable(origin
, stanza
); -- send unavailable
775 local o_data
= self
:get_occupant_by_nick(to
);
777 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Recipient not in room"));
780 log("debug", "%s sent private message stanza to %s (%s)", from
, to
, o_data
.jid
);
781 stanza
:tag("x", { xmlns
= "http://jabber.org/protocol/muc#user" }):up();
782 stanza
.attr
.from
= current_nick
;
783 self
:route_to_occupant(o_data
, stanza
)
784 -- TODO: Remove x tag?
785 stanza
.attr
.from
= from
;
789 function room_mt
:send_form(origin
, stanza
)
790 origin
.send(st
.reply(stanza
):query("http://jabber.org/protocol/muc#owner")
791 :add_child(self
:get_form_layout(stanza
.attr
.from
):form())
795 function room_mt
:get_form_layout(actor
)
796 local form
= dataform
.new({
797 title
= "Configuration for "..self
.jid
,
798 instructions
= "Complete and submit this form to configure the room.",
802 value
= 'http://jabber.org/protocol/muc#roomconfig'
805 return module
:fire_event("muc-config-form", { room
= self
, actor
= actor
, form
= form
}) or form
;
808 function room_mt
:process_form(origin
, stanza
)
809 local form
= stanza
.tags
[1]:get_child("x", "jabber:x:data");
810 if form
.attr
.type == "cancel" then
811 origin
.send(st
.reply(stanza
));
812 elseif form
.attr
.type == "submit" then
813 local fields
, errors
, present
;
814 if form
.tags
[1] == nil then -- Instant room
815 fields
, present
= {}, {};
817 fields
, errors
, present
= self
:get_form_layout(stanza
.attr
.from
):data(form
);
818 if fields
.FORM_TYPE
~= "http://jabber.org/protocol/muc#roomconfig" then
819 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request", "Form is not of type room configuration"));
830 actor
= stanza
.attr
.from
;
832 function event
.update_option(name
, field
, allowed
)
833 local new
= fields
[field
];
834 if new
== nil then return; end
835 if allowed
and not allowed
[new
] then return; end
836 if new
== self
["get_"..name
](self
) then return; end
837 event
.status_codes
["104"] = true;
838 self
["set_"..name
](self
, new
);
841 module
:fire_event("muc-config-submitted", event
);
842 for submitted_field
in pairs(present
) do
843 event
.field
, event
.value
= submitted_field
, fields
[submitted_field
];
844 module
:fire_event("muc-config-submitted/"..submitted_field
, event
);
846 event
.field
, event
.value
= nil, nil;
849 origin
.send(st
.reply(stanza
));
851 if next(event
.status_codes
) then
852 local msg
= st
.message({type='groupchat', from
=self
.jid
})
853 :tag('x', {xmlns
='http://jabber.org/protocol/muc#user'})
854 for code
in pairs(event
.status_codes
) do
855 msg
:tag("status", {code
= code
;}):up();
858 self
:broadcast_message(msg
);
861 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request", "Not a submitted form"));
866 -- Removes everyone from the room
867 function room_mt
:clear(x
)
868 x
= x
or st
.stanza("x", {xmlns
='http://jabber.org/protocol/muc#user'});
869 local occupants_updated
= {};
870 for nick
, occupant
in self
:each_occupant() do -- luacheck: ignore 213
872 self
:save_occupant(occupant
);
873 occupants_updated
[occupant
] = true;
875 for occupant
in pairs(occupants_updated
) do
876 self
:publicise_occupant_status(occupant
, x
);
877 module
:fire_event("muc-occupant-left", { room
= self
; nick
= occupant
.nick
; occupant
= occupant
;});
881 function room_mt
:destroy(newjid
, reason
, password
)
882 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user"})
883 :tag("destroy", {jid
=newjid
});
884 if reason
then x
:tag("reason"):text(reason
):up(); end
885 if password
then x
:tag("password"):text(password
):up(); end
887 self
.destroying
= reason
or true;
889 module
:fire_event("muc-room-destroyed", { room
= self
, reason
= reason
, newjid
= newjid
, password
= password
});
893 function room_mt
:handle_disco_info_get_query(origin
, stanza
)
894 origin
.send(self
:get_disco_info(stanza
));
898 function room_mt
:handle_disco_items_get_query(origin
, stanza
)
899 origin
.send(self
:get_disco_items(stanza
));
903 function room_mt
:handle_admin_query_set_command(origin
, stanza
)
904 local item
= stanza
.tags
[1].tags
[1];
906 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request"));
909 if item
.attr
.jid
then -- Validate provided JID
910 item
.attr
.jid
= jid_prep(item
.attr
.jid
);
911 if not item
.attr
.jid
then
912 origin
.send(st
.error_reply(stanza
, "modify", "jid-malformed"));
916 if item
.attr
.nick
then -- Validate provided nick
917 item
.attr
.nick
= resourceprep(item
.attr
.nick
);
918 if not item
.attr
.nick
then
919 origin
.send(st
.error_reply(stanza
, "modify", "jid-malformed", "invalid nickname"));
923 if not item
.attr
.jid
and item
.attr
.nick
then
924 -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
925 local occupant
= self
:get_occupant_by_nick(self
.jid
.."/"..item
.attr
.nick
);
926 if occupant
then item
.attr
.jid
= occupant
.bare_jid
; end
927 elseif item
.attr
.role
and not item
.attr
.nick
and item
.attr
.jid
then
928 -- Role changes should use nick, but we have a JID so pull the nick from that
929 local nick
= self
:get_occupant_jid(item
.attr
.jid
);
930 if nick
then item
.attr
.nick
= jid_resource(nick
); end
932 local actor
= stanza
.attr
.from
;
933 local reason
= item
:get_child_text("reason");
934 local success
, errtype
, err
935 if item
.attr
.affiliation
and item
.attr
.jid
and not item
.attr
.role
then
936 local registration_data
;
937 if item
.attr
.nick
then
938 local room_nick
= self
.jid
.."/"..item
.attr
.nick
;
939 local existing_occupant
= self
:get_occupant_by_nick(room_nick
);
940 if existing_occupant
and existing_occupant
.bare_jid
~= item
.attr
.jid
then
941 module
:log("debug", "Existing occupant for %s: %s does not match %s", room_nick
, existing_occupant
.bare_jid
, item
.attr
.jid
);
942 self
:set_role(true, room_nick
, nil, "This nickname is reserved");
944 module
:log("debug", "Reserving %s for %s (%s)", item
.attr
.nick
, item
.attr
.jid
, item
.attr
.affiliation
);
945 registration_data
= { reserved_nickname
= item
.attr
.nick
};
947 success
, errtype
, err
= self
:set_affiliation(actor
, item
.attr
.jid
, item
.attr
.affiliation
, reason
, registration_data
);
948 elseif item
.attr
.role
and item
.attr
.nick
and not item
.attr
.affiliation
then
949 success
, errtype
, err
= self
:set_role(actor
, self
.jid
.."/"..item
.attr
.nick
, item
.attr
.role
, reason
);
951 success
, errtype
, err
= nil, "cancel", "bad-request";
955 origin
.send(st
.error_reply(stanza
, errtype
, err
));
957 origin
.send(st
.reply(stanza
));
962 function room_mt
:handle_admin_query_get_command(origin
, stanza
)
963 local actor
= stanza
.attr
.from
;
964 local affiliation
= self
:get_affiliation(actor
);
965 local item
= stanza
.tags
[1].tags
[1];
966 local _aff
= item
.attr
.affiliation
;
967 local _aff_rank
= valid_affiliations
[_aff
or "none"];
968 local _rol
= item
.attr
.role
;
969 if _aff
and _aff_rank
and not _rol
then
970 -- You need to be at least an admin, and be requesting info about your affifiliation or lower
971 -- e.g. an admin can't ask for a list of owners
972 local affiliation_rank
= valid_affiliations
[affiliation
or "none"];
973 if (affiliation_rank
>= valid_affiliations
.admin
and affiliation_rank
>= _aff_rank
)
974 or (self
:get_whois() == "anyone") then
975 local reply
= st
.reply(stanza
):query("http://jabber.org/protocol/muc#admin");
976 for jid
in self
:each_affiliation(_aff
or "none") do
977 local nick
= self
:get_registered_nick(jid
);
978 reply
:tag("item", {affiliation
= _aff
, jid
= jid
, nick
= nick
}):up();
980 origin
.send(reply
:up());
983 origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
986 elseif _rol
and valid_roles
[_rol
or "none"] and not _aff
then
987 local role
= self
:get_role(self
:get_occupant_jid(actor
)) or self
:get_default_role(affiliation
);
988 if valid_roles
[role
or "none"] >= valid_roles
.moderator
then
989 if _rol
== "none" then _rol
= nil; end
990 local reply
= st
.reply(stanza
):query("http://jabber.org/protocol/muc#admin");
991 -- TODO: whois check here? (though fully anonymous rooms are not supported)
992 for occupant_jid
, occupant
in self
:each_occupant() do
993 if occupant
.role
== _rol
then
994 local nick
= jid_resource(occupant_jid
);
995 self
:build_item_list(occupant
, reply
, false, nick
);
998 origin
.send(reply
:up());
1001 origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
1005 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request"));
1010 function room_mt
:handle_owner_query_get_to_room(origin
, stanza
)
1011 if self
:get_affiliation(stanza
.attr
.from
) ~= "owner" then
1012 origin
.send(st
.error_reply(stanza
, "auth", "forbidden", "Only owners can configure rooms"));
1016 self
:send_form(origin
, stanza
);
1019 function room_mt
:handle_owner_query_set_to_room(origin
, stanza
)
1020 if self
:get_affiliation(stanza
.attr
.from
) ~= "owner" then
1021 origin
.send(st
.error_reply(stanza
, "auth", "forbidden", "Only owners can configure rooms"));
1025 local child
= stanza
.tags
[1].tags
[1];
1027 origin
.send(st
.error_reply(stanza
, "modify", "bad-request"));
1029 elseif child
.name
== "destroy" then
1030 local newjid
= child
.attr
.jid
;
1031 local reason
= child
:get_child_text("reason");
1032 local password
= child
:get_child_text("password");
1033 self
:destroy(newjid
, reason
, password
);
1034 origin
.send(st
.reply(stanza
));
1036 elseif child
.name
== "x" and child
.attr
.xmlns
== "jabber:x:data" then
1037 return self
:process_form(origin
, stanza
);
1039 origin
.send(st
.error_reply(stanza
, "cancel", "service-unavailable"));
1044 function room_mt
:handle_groupchat_to_room(origin
, stanza
)
1045 local from
= stanza
.attr
.from
;
1046 local occupant
= self
:get_occupant_by_real_jid(from
);
1047 if module
:fire_event("muc-occupant-groupchat", {
1048 room
= self
; origin
= origin
; stanza
= stanza
; from
= from
; occupant
= occupant
;
1049 }) then return true; end
1050 stanza
.attr
.from
= occupant
.nick
;
1051 self
:broadcast_message(stanza
);
1052 stanza
.attr
.from
= from
;
1057 module
:hook("muc-occupant-groupchat", function(event
)
1058 local role_rank
= valid_roles
[event
.occupant
and event
.occupant
.role
or "none"];
1059 if role_rank
<= valid_roles
.none
then
1060 event
.origin
.send(st
.error_reply(event
.stanza
, "cancel", "not-acceptable"));
1062 elseif role_rank
<= valid_roles
.visitor
then
1063 event
.origin
.send(st
.error_reply(event
.stanza
, "auth", "forbidden"));
1068 -- hack - some buggy clients send presence updates to the room rather than their nick
1069 function room_mt
:handle_presence_to_room(origin
, stanza
)
1070 local current_nick
= self
:get_occupant_jid(stanza
.attr
.from
);
1072 if current_nick
then
1073 local to
= stanza
.attr
.to
;
1074 stanza
.attr
.to
= current_nick
;
1075 handled
= self
:handle_presence_to_occupant(origin
, stanza
);
1076 stanza
.attr
.to
= to
;
1081 -- Need visitor role or higher to invite
1082 module
:hook("muc-pre-invite", function(event
)
1083 local room
, stanza
= event
.room
, event
.stanza
;
1084 local _from
= stanza
.attr
.from
;
1085 local inviter
= room
:get_occupant_by_real_jid(_from
);
1086 local role
= inviter
and inviter
.role
or room
:get_default_role(room
:get_affiliation(_from
));
1087 if valid_roles
[role
or "none"] <= valid_roles
.visitor
then
1088 event
.origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
1093 function room_mt
:handle_mediated_invite(origin
, stanza
)
1094 local payload
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite");
1095 local invitee
= jid_prep(payload
.attr
.to
);
1097 origin
.send(st
.error_reply(stanza
, "cancel", "jid-malformed"));
1099 elseif module
:fire_event("muc-pre-invite", {room
= self
, origin
= origin
, stanza
= stanza
}) then
1102 local invite
= muc_util
.filter_muc_x(st
.clone(stanza
));
1103 invite
.attr
.from
= self
.jid
;
1104 invite
.attr
.to
= invitee
;
1105 invite
:tag('x', {xmlns
='http://jabber.org/protocol/muc#user'})
1106 :tag('invite', {from
= stanza
.attr
.from
;})
1107 :tag('reason'):text(payload
:get_child_text("reason")):up()
1110 if not module
:fire_event("muc-invite", {room
= self
, stanza
= invite
, origin
= origin
, incoming
= stanza
}) then
1111 self
:route_stanza(invite
);
1116 -- COMPAT: Some older clients expect this
1117 module
:hook("muc-invite", function(event
)
1118 local room
, stanza
= event
.room
, event
.stanza
;
1119 local invite
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite");
1120 local reason
= invite
:get_child_text("reason");
1121 stanza
:tag('x', {xmlns
= "jabber:x:conference"; jid
= room
.jid
;})
1126 -- Add a plain message for clients which don't support invites
1127 module
:hook("muc-invite", function(event
)
1128 local room
, stanza
= event
.room
, event
.stanza
;
1129 if not stanza
:get_child("body") then
1130 local invite
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite");
1131 local reason
= invite
:get_child_text("reason") or "";
1133 :text(invite
.attr
.from
.." invited you to the room "..room
.jid
..(reason
~= "" and (" ("..reason
..")") or ""))
1138 function room_mt
:handle_mediated_decline(origin
, stanza
)
1139 local payload
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline");
1140 local declinee
= jid_prep(payload
.attr
.to
);
1141 if not declinee
then
1142 origin
.send(st
.error_reply(stanza
, "cancel", "jid-malformed"));
1144 elseif module
:fire_event("muc-pre-decline", {room
= self
, origin
= origin
, stanza
= stanza
}) then
1147 local decline
= muc_util
.filter_muc_x(st
.clone(stanza
));
1148 decline
.attr
.from
= self
.jid
;
1149 decline
.attr
.to
= declinee
;
1150 decline
:tag("x", {xmlns
= "http://jabber.org/protocol/muc#user"})
1151 :tag("decline", {from
= stanza
.attr
.from
})
1152 :tag("reason"):text(payload
:get_child_text("reason")):up()
1155 if not module
:fire_event("muc-decline", {room
= self
, stanza
= decline
, origin
= origin
, incoming
= stanza
}) then
1156 declinee
= decline
.attr
.to
; -- re-fetch, in case event modified it
1158 if jid_bare(declinee
) == self
.jid
then -- declinee jid is already an in-room jid
1159 occupant
= self
:get_occupant_by_nick(declinee
);
1162 self
:route_to_occupant(occupant
, decline
);
1164 self
:route_stanza(decline
);
1170 -- Add a plain message for clients which don't support declines
1171 module
:hook("muc-decline", function(event
)
1172 local room
, stanza
= event
.room
, event
.stanza
;
1173 if not stanza
:get_child("body") then
1174 local decline
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline");
1175 local reason
= decline
:get_child_text("reason") or "";
1176 stanza
:body(decline
.attr
.from
.." declined your invite to the room "
1177 ..room
.jid
..(reason
~= "" and (" ("..reason
..")") or ""));
1181 function room_mt
:handle_message_to_room(origin
, stanza
)
1182 local type = stanza
.attr
.type;
1183 if type == "groupchat" then
1184 return self
:handle_groupchat_to_room(origin
, stanza
)
1185 elseif type == "error" and is_kickable_error(stanza
) then
1186 return self
:handle_kickable(origin
, stanza
)
1187 elseif type == nil or type == "normal" then
1188 local x
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user");
1190 local payload
= x
.tags
[1];
1191 if payload
== nil then --luacheck: ignore 542
1193 elseif payload
.name
== "invite" and payload
.attr
.to
then
1194 return self
:handle_mediated_invite(origin
, stanza
)
1195 elseif payload
.name
== "decline" and payload
.attr
.to
then
1196 return self
:handle_mediated_decline(origin
, stanza
)
1198 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request"));
1202 local form
= stanza
:get_child("x", "jabber:x:data");
1203 local form_type
= dataform
.get_type(form
);
1204 if form_type
== "http://jabber.org/protocol/muc#request" then
1205 self
:handle_role_request(origin
, stanza
, form
);
1211 function room_mt
:route_stanza(stanza
) -- luacheck: ignore 212
1212 module
:send(stanza
);
1215 function room_mt
:get_affiliation(jid
)
1216 local node
, host
, resource
= jid_split(jid
);
1217 -- Affiliations are granted, revoked, and maintained based on the user's bare JID.
1218 local bare
= node
and node
.."@"..host
or host
;
1219 local result
= self
._affiliations
[bare
];
1220 if not result
and self
._affiliations
[host
] == "outcast" then result
= "outcast"; end -- host banned
1224 -- Iterates over jid, affiliation pairs
1225 function room_mt
:each_affiliation(with_affiliation
)
1226 local _affiliations
, _affiliation_data
= self
._affiliations
, self
._affiliation_data
;
1227 return function(_
, jid
)
1229 repeat -- Iterate until we get a match
1230 jid
, affiliation
= next(_affiliations
, jid
);
1231 until with_affiliation
== nil or jid
== nil or affiliation
== with_affiliation
1232 return jid
, affiliation
, _affiliation_data
[jid
];
1236 function room_mt
:set_affiliation(actor
, jid
, affiliation
, reason
, data
)
1237 if not actor
then return nil, "modify", "not-acceptable"; end;
1239 local node
, host
, resource
= jid_split(jid
);
1240 if not host
then return nil, "modify", "not-acceptable"; end
1241 jid
= jid_join(node
, host
); -- Bare
1242 local is_host_only
= node
== nil;
1244 if valid_affiliations
[affiliation
or "none"] == nil then
1245 return nil, "modify", "not-acceptable";
1247 affiliation
= affiliation
~= "none" and affiliation
or nil; -- coerces `affiliation == false` to `nil`
1249 local target_affiliation
= self
._affiliations
[jid
]; -- Raw; don't want to check against host
1250 local is_downgrade
= valid_affiliations
[target_affiliation
or "none"] > valid_affiliations
[affiliation
or "none"];
1252 if actor
== true then
1253 actor
= nil -- So we can pass it safely to 'publicise_occupant_status' below
1255 local actor_affiliation
= self
:get_affiliation(actor
);
1256 if actor_affiliation
== "owner" then
1257 if jid_bare(actor
) == jid
and is_downgrade
then -- self change
1258 -- need at least one owner
1259 local is_last
= true;
1260 for j
in self
:each_affiliation("owner") do
1261 if j
~= jid
then is_last
= false; break; end
1264 return nil, "cancel", "conflict";
1267 -- owners can do anything else
1268 elseif affiliation
== "owner" or affiliation
== "admin"
1269 or actor_affiliation
~= "admin"
1270 or target_affiliation
== "owner" or target_affiliation
== "admin" then
1271 -- Can't demote owners or other admins
1272 return nil, "cancel", "not-allowed";
1276 -- Set in 'database'
1277 self
._affiliations
[jid
] = affiliation
;
1278 if not affiliation
or data
== false or (data
~= nil and next(data
) == nil) then
1279 module
:log("debug", "Clearing affiliation data for %s", jid
);
1280 self
._affiliation_data
[jid
] = nil;
1282 module
:log("debug", "Updating affiliation data for %s", jid
);
1283 self
._affiliation_data
[jid
] = data
;
1287 local role
= self
:get_default_role(affiliation
);
1288 local role_rank
= valid_roles
[role
or "none"];
1289 local occupants_updated
= {}; -- Filled with old roles
1290 for nick
, occupant
in self
:each_occupant() do -- luacheck: ignore 213
1291 if occupant
.bare_jid
== jid
or (
1292 -- Outcast can be by host.
1293 is_host_only
and affiliation
== "outcast" and select(2, jid_split(occupant
.bare_jid
)) == host
1295 -- need to publcize in all cases; as affiliation in <item/> has changed.
1296 occupants_updated
[occupant
] = occupant
.role
;
1297 if occupant
.role
~= role
and (
1299 valid_roles
[occupant
.role
or "none"] < role_rank
-- upgrade
1301 occupant
.role
= role
;
1302 self
:save_occupant(occupant
);
1307 -- Tell the room of the new occupant affiliations+roles
1308 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user"});
1309 if not role
then -- getting kicked
1310 if affiliation
== "outcast" then
1311 x
:tag("status", {code
="301"}):up(); -- banned
1313 x
:tag("status", {code
="321"}):up(); -- affiliation change
1316 local is_semi_anonymous
= self
:get_whois() == "moderators";
1318 if next(occupants_updated
) ~= nil then
1319 for occupant
, old_role
in pairs(occupants_updated
) do
1320 self
:publicise_occupant_status(occupant
, x
, nil, actor
, reason
);
1321 if occupant
.role
== nil then
1322 module
:fire_event("muc-occupant-left", {room
= self
; nick
= occupant
.nick
; occupant
= occupant
;});
1323 elseif is_semi_anonymous
and
1324 (old_role
== "moderator" and occupant
.role
~= "moderator") or
1325 (old_role
~= "moderator" and occupant
.role
== "moderator") then -- Has gained or lost moderator status
1326 -- Send everyone else's presences (as jid visibility has changed)
1327 for real_jid
in occupant
:each_session() do
1328 self
:send_occupant_list(real_jid
, function(occupant_jid
, occupant
) --luacheck: ignore 212 433
1329 return occupant
.bare_jid
~= jid
;
1335 -- Announce affiliation change for a user that is not currently in the room,
1336 -- XEP-0045 (v1.31.2) example 195
1337 -- add_item(x, affiliation, role, jid, nick, actor_nick, actor_jid, reason)
1338 local announce_msg
= st
.message({ from
= self
.jid
})
1339 :add_child(add_item(st
.clone(x
), affiliation
, nil, jid
, nil, nil, nil, reason
));
1340 local min_role
= is_semi_anonymous
and "moderator" or "none";
1341 self
:broadcast(announce_msg
, muc_util
.only_with_min_role(min_role
));
1346 module
:fire_event("muc-set-affiliation", {
1350 affiliation
= affiliation
or "none";
1352 previous_affiliation
= target_affiliation
;
1353 data
= data
and data
or nil; -- coerce false to nil
1354 in_room
= next(occupants_updated
) ~= nil;
1360 function room_mt
:get_affiliation_data(jid
, key
)
1361 local data
= self
._affiliation_data
[jid
];
1362 if not data
then return nil; end
1369 function room_mt
:get_role(nick
)
1370 local occupant
= self
:get_occupant_by_nick(nick
);
1371 return occupant
and occupant
.role
or nil;
1374 function room_mt
:set_role(actor
, occupant_jid
, role
, reason
)
1375 if not actor
then return nil, "modify", "not-acceptable"; end
1377 local occupant
= self
:get_occupant_by_nick(occupant_jid
);
1378 if not occupant
then return nil, "modify", "item-not-found"; end
1380 if valid_roles
[role
or "none"] == nil then
1381 return nil, "modify", "not-acceptable";
1383 role
= role
~= "none" and role
or nil; -- coerces `role == false` to `nil`
1385 if actor
== true then
1386 actor
= nil -- So we can pass it safely to 'publicise_occupant_status' below
1388 -- Can't do anything to other owners or admins
1389 local occupant_affiliation
= self
:get_affiliation(occupant
.bare_jid
);
1390 if occupant_affiliation
== "owner" or occupant_affiliation
== "admin" then
1391 return nil, "cancel", "not-allowed";
1394 -- If you are trying to give or take moderator role you need to be an owner or admin
1395 if occupant
.role
== "moderator" or role
== "moderator" then
1396 local actor_affiliation
= self
:get_affiliation(actor
);
1397 if actor_affiliation
~= "owner" and actor_affiliation
~= "admin" then
1398 return nil, "cancel", "not-allowed";
1402 -- Need to be in the room and a moderator
1403 local actor_occupant
= self
:get_occupant_by_real_jid(actor
);
1404 if not actor_occupant
or actor_occupant
.role
~= "moderator" then
1405 return nil, "cancel", "not-allowed";
1409 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user"});
1411 x
:tag("status", {code
= "307"}):up();
1413 occupant
.role
= role
;
1414 self
:save_occupant(occupant
);
1415 self
:publicise_occupant_status(occupant
, x
, nil, actor
, reason
);
1417 module
:fire_event("muc-occupant-left", {room
= self
; nick
= occupant
.nick
; occupant
= occupant
;});
1422 local whois
= module
:require
"muc/whois";
1423 room_mt
.get_whois
= whois
.get
;
1424 room_mt
.set_whois
= whois
.set
;
1426 local _M
= {}; -- module "muc"
1428 function _M
.new_room(jid
, config
)
1429 return setmetatable({
1433 _data
= config
or {};
1435 _affiliation_data
= {};
1439 local new_format
= module
:get_option_boolean("new_muc_storage_format", false);
1441 function room_mt
:freeze(live
)
1442 local frozen
, state
;
1448 for user
, affiliation
in pairs(self
._affiliations
) do
1449 frozen
[user
] = affiliation
;
1455 _affiliations
= self
._affiliations
;
1456 _affiliation_data
= self
._affiliation_data
;
1461 for nick
, occupant
in self
:each_occupant() do
1463 bare_jid
= occupant
.bare_jid
;
1464 role
= occupant
.role
;
1467 for jid
, presence
in occupant
:each_session() do
1468 state
[jid
] = st
.preserialize(presence
);
1471 local history
= self
._history
;
1472 if history
and history
[1] ~= nil then
1473 state
._last_message
= st
.preserialize(history
[#history
].stanza
);
1474 state
._last_message_at
= history
[#history
].timestamp
;
1477 return frozen
, state
;
1480 function _M
.restore_room(frozen
, state
)
1481 local room_jid
= frozen
._jid
or frozen
.jid
;
1482 local room
= _M
.new_room(room_jid
, frozen
._data
);
1484 if state
and state
._last_message
and state
._last_message_at
then
1486 { stanza
= st
.deserialize(state
._last_message
),
1487 timestamp
= state
._last_message_at
, },
1491 local occupants
= {};
1492 local room_name
, room_host
= jid_split(room_jid
);
1494 room
._affiliation_data
= frozen
._affiliation_data
or {};
1496 if frozen
.jid
and frozen
._affiliations
then
1497 -- Old storage format
1498 room
._affiliations
= frozen
._affiliations
;
1500 -- New storage format
1501 for jid
, data
in pairs(frozen
) do
1502 local node
, host
, resource
= jid_split(jid
);
1503 if host
:sub(1,1) ~= "_" and not resource
and type(data
) == "string" then
1504 -- bare jid: affiliation
1505 room
._affiliations
[jid
] = data
;
1509 for jid
, data
in pairs(state
or frozen
) do
1510 local node
, host
, resource
= jid_split(jid
);
1511 if node
or host
:sub(1,1) ~= "_" then
1512 if host
== room_host
and node
== room_name
and resource
and type(data
) == "table" then
1513 -- full room jid: bare real jid and role
1515 local occupant
= occupants
[nick
] or occupant_lib
.new(data
.bare_jid
, nick
);
1516 occupant
.bare_jid
= data
.bare_jid
;
1517 occupant
.role
= data
.role
;
1518 occupant
.jid
= data
.jid
; -- Primary session JID
1519 occupants
[nick
] = occupant
;
1520 elseif type(data
) == "table" and data
.name
== "presence" then
1521 -- full user jid: presence
1522 local nick
= data
.attr
.from
;
1523 local occupant
= occupants
[nick
] or occupant_lib
.new(nil, nick
);
1524 local presence
= st
.deserialize(data
);
1525 occupant
:set_session(jid
, presence
);
1526 occupants
[nick
] = occupant
;
1531 for _
, occupant
in pairs(occupants
) do
1532 room
:save_occupant(occupant
);
1538 _M
.room_mt
= room_mt
;