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
;
26 local new_id
= require
"util.id".medium
;
28 local log = module
._log
;
30 local occupant_lib
= module
:require
"muc/occupant"
31 local muc_util
= module
:require
"muc/util";
32 local is_kickable_error
= muc_util
.is_kickable_error
;
33 local valid_roles
, valid_affiliations
= muc_util
.valid_roles
, muc_util
.valid_affiliations
;
36 room_mt
.__index
= room_mt
;
38 function room_mt
:__tostring()
39 return "MUC room ("..self
.jid
..")";
42 function room_mt
.save()
43 -- overridden by mod_muc.lua
46 function room_mt
:get_occupant_jid(real_jid
)
47 return self
._jid_nick
[real_jid
]
50 function room_mt
:get_default_role(affiliation
)
51 local role
= module
:fire_event("muc-get-default-role", {
53 affiliation
= affiliation
;
54 affiliation_rank
= valid_affiliations
[affiliation
or "none"];
56 role
= role
~= "none" and role
or nil; -- coerces `role == false` to `nil`
57 return role
, valid_roles
[role
or "none"];
59 module
:hook("muc-get-default-role", function(event
)
60 if event
.affiliation_rank
>= valid_affiliations
.admin
then
62 elseif event
.affiliation_rank
>= valid_affiliations
.none
then
67 --- Occupant functions
68 function room_mt
:new_occupant(bare_real_jid
, nick
)
69 local occupant
= occupant_lib
.new(bare_real_jid
, nick
);
70 local affiliation
= self
:get_affiliation(bare_real_jid
);
71 occupant
.role
= self
:get_default_role(affiliation
);
75 -- nick is in the form of an in-room JID
76 function room_mt
:get_occupant_by_nick(nick
)
77 local occupant
= self
._occupants
[nick
];
78 if occupant
== nil then return nil end
79 return occupant_lib
.copy(occupant
);
83 local function next_copied_occupant(occupants
, occupant_jid
)
84 local next_occupant_jid
, raw_occupant
= next(occupants
, occupant_jid
);
85 if next_occupant_jid
== nil then return nil end
86 return next_occupant_jid
, occupant_lib
.copy(raw_occupant
);
88 -- FIXME Explain what 'read_only' is supposed to be
89 function room_mt
:each_occupant(read_only
) -- luacheck: ignore 212
90 return next_copied_occupant
, self
._occupants
, nil;
94 function room_mt
:has_occupant()
95 return next(self
._occupants
, nil) ~= nil
98 function room_mt
:get_occupant_by_real_jid(real_jid
)
99 local occupant_jid
= self
:get_occupant_jid(real_jid
);
100 if occupant_jid
== nil then return nil end
101 return self
:get_occupant_by_nick(occupant_jid
);
104 function room_mt
:save_occupant(occupant
)
105 occupant
= occupant_lib
.copy(occupant
); -- So that occupant can be modified more
106 local id
= occupant
.nick
108 -- Need to maintain _jid_nick secondary index
109 local old_occupant
= self
._occupants
[id
];
111 for real_jid
in old_occupant
:each_session() do
112 self
._jid_nick
[real_jid
] = nil;
116 local has_live_session
= false
117 if occupant
.role
~= nil then
118 for real_jid
, presence
in occupant
:each_session() do
119 if presence
.attr
.type == nil then
120 has_live_session
= true
121 self
._jid_nick
[real_jid
] = occupant
.nick
;
124 if not has_live_session
then
125 -- Has no live sessions left; they have left the room.
129 if not has_live_session
then
132 self
._occupants
[id
] = occupant
136 function room_mt
:route_to_occupant(occupant
, stanza
)
137 local to
= stanza
.attr
.to
;
138 for jid
in occupant
:each_session() do
139 stanza
.attr
.to
= jid
;
140 self
:route_stanza(stanza
);
145 -- actor is the attribute table
146 local function add_item(x
, affiliation
, role
, jid
, nick
, actor_nick
, actor_jid
, reason
)
147 x
:tag("item", {affiliation
= affiliation
; role
= role
; jid
= jid
; nick
= nick
;})
148 if actor_nick
or actor_jid
then
149 x
:tag("actor", {nick
= actor_nick
; jid
= actor_jid
;}):up()
152 x
:tag("reason"):text(reason
):up()
158 -- actor is (real) jid
159 function room_mt
:build_item_list(occupant
, x
, is_anonymous
, nick
, actor_nick
, actor_jid
, reason
)
160 local affiliation
= self
:get_affiliation(occupant
.bare_jid
) or "none";
161 local role
= occupant
.role
or "none";
163 add_item(x
, affiliation
, role
, nil, nick
, actor_nick
, actor_jid
, reason
);
165 for real_jid
in occupant
:each_session() do
166 add_item(x
, affiliation
, role
, real_jid
, nick
, actor_nick
, actor_jid
, reason
);
172 function room_mt
:broadcast_message(stanza
)
173 if module
:fire_event("muc-broadcast-message", {room
= self
, stanza
= stanza
}) then
176 self
:broadcast(stanza
);
180 -- Strip delay tags claiming to be from us
181 module
:hook("muc-occupant-groupchat", function (event
)
182 local stanza
= event
.stanza
;
183 local room
= event
.room
;
184 local room_jid
= room
.jid
;
186 stanza
:maptags(function (child
)
187 if child
.name
== "delay" and child
.attr
["xmlns"] == "urn:xmpp:delay" then
188 if child
.attr
["from"] == room_jid
then
192 if child
.name
== "x" and child
.attr
["xmlns"] == "jabber:x:delay" then
193 if child
.attr
["from"] == room_jid
then
201 -- Broadcast a stanza to all occupants in the room.
202 -- optionally checks conditional called with (nick, occupant)
203 function room_mt
:broadcast(stanza
, cond_func
)
204 for nick
, occupant
in self
:each_occupant() do
205 if cond_func
== nil or cond_func(nick
, occupant
) then
206 self
:route_to_occupant(occupant
, stanza
)
211 local function can_see_real_jids(whois
, occupant
)
212 if whois
== "anyone" then
214 elseif whois
== "moderators" then
215 return valid_roles
[occupant
.role
or "none"] >= valid_roles
.moderator
;
219 -- Broadcasts an occupant's presence to the whole room
220 -- Takes the x element that goes into the stanzas
221 function room_mt
:publicise_occupant_status(occupant
, x
, nick
, actor
, reason
)
222 local base_x
= x
.base
or x
;
223 -- Build real jid and (optionally) occupant jid template presences
224 local base_presence
do
225 -- Try to use main jid's presence
226 local pr
= occupant
:get_presence();
227 if pr
and (occupant
.role
~= nil or pr
.attr
.type == "unavailable") then
228 base_presence
= st
.clone(pr
);
229 else -- user is leaving but didn't send a leave presence. make one for them
230 base_presence
= st
.presence
{from
= occupant
.nick
; type = "unavailable";};
234 -- Fire event (before full_p and anon_p are created)
236 room
= self
; stanza
= base_presence
; x
= base_x
;
237 occupant
= occupant
; nick
= nick
; actor
= actor
;
240 module
:fire_event("muc-broadcast-presence", event
);
242 -- Allow muc-broadcast-presence listeners to change things
245 reason
= event
.reason
;
247 local whois
= self
:get_whois();
251 actor_nick
= jid_resource(self
:get_occupant_jid(actor
));
254 local full_p
, full_x
;
255 local function get_full_p()
256 if full_p
== nil then
257 full_x
= st
.clone(x
.full
or base_x
);
258 self
:build_item_list(occupant
, full_x
, false, nick
, actor_nick
, actor
, reason
);
259 full_p
= st
.clone(base_presence
):add_child(full_x
);
261 return full_p
, full_x
;
264 local anon_p
, anon_x
;
265 local function get_anon_p()
266 if anon_p
== nil then
267 anon_x
= st
.clone(x
.anon
or base_x
);
268 self
:build_item_list(occupant
, anon_x
, true, nick
, actor_nick
, nil, reason
);
269 anon_p
= st
.clone(base_presence
):add_child(anon_x
);
271 return anon_p
, anon_x
;
274 local self_p
, self_x
;
276 -- Can always see your own full jids
277 -- But not allowed to see actor's
278 self_x
= st
.clone(x
.self
or base_x
);
279 self
:build_item_list(occupant
, self_x
, false, nick
, actor_nick
, nil, reason
);
280 self_p
= st
.clone(base_presence
):add_child(self_x
);
284 for occupant_nick
, n_occupant
in self
:each_occupant() do
285 if occupant_nick
~= occupant
.nick
then
287 if can_see_real_jids(whois
, n_occupant
) then
289 elseif occupant
.bare_jid
== n_occupant
.bare_jid
then
294 self
:route_to_occupant(n_occupant
, pr
);
298 -- Presences for occupant itself
299 self_x
:tag("status", {code
= "110";}):up();
300 if occupant
.role
== nil then
301 -- They get an unavailable
302 self
:route_to_occupant(occupant
, self_p
);
304 -- use their own presences as templates
305 for full_jid
, pr
in occupant
:each_session() do
307 pr
.attr
.to
= full_jid
;
308 pr
:add_child(self_x
);
309 self
:route_stanza(pr
);
314 function room_mt
:send_occupant_list(to
, filter
)
315 local to_bare
= jid_bare(to
);
316 local is_anonymous
= false;
317 local whois
= self
:get_whois();
318 if whois
~= "anyone" then
319 local affiliation
= self
:get_affiliation(to
);
320 if affiliation
~= "admin" and affiliation
~= "owner" then
321 local occupant
= self
:get_occupant_by_real_jid(to
);
322 if not (occupant
and can_see_real_jids(whois
, occupant
)) then
327 for occupant_jid
, occupant
in self
:each_occupant() do
328 if filter
== nil or filter(occupant_jid
, occupant
) then
329 local x
= st
.stanza("x", {xmlns
='http://jabber.org/protocol/muc#user'});
330 self
:build_item_list(occupant
, x
, is_anonymous
and to_bare
~= occupant
.bare_jid
); -- can always see your own jids
331 local pres
= st
.clone(occupant
:get_presence());
334 self
:route_stanza(pres
);
339 function room_mt
:get_disco_info(stanza
)
340 local node
= stanza
.tags
[1].attr
.node
or "";
341 local reply
= st
.reply(stanza
):tag("query", { xmlns
= "http://jabber.org/protocol/disco#info", node
= node
});
342 local event_name
= "muc-disco#info";
343 local event_data
= { room
= self
, reply
= reply
, stanza
= stanza
};
346 event_name
= event_name
.."/"..node
;
348 event_data
.form
= dataform
.new
{
349 { name
= "FORM_TYPE", type = "hidden", value
= "http://jabber.org/protocol/muc#roominfo" };
351 event_data
.formdata
= {};
353 module
:fire_event(event_name
, event_data
);
354 if event_data
.form
then
355 reply
:add_child(event_data
.form
:form(event_data
.formdata
, "result"));
359 module
:hook("muc-disco#info", function(event
)
360 event
.reply
:tag("feature", {var
= "http://jabber.org/protocol/muc"}):up();
361 event
.reply
:tag("feature", {var
= "http://jabber.org/protocol/muc#stable_id"}):up();
362 event
.reply
:tag("feature", {var
= "http://jabber.org/protocol/muc#self-ping-optimization"}):up();
364 module
:hook("muc-disco#info", function(event
)
365 table.insert(event
.form
, { name
= "muc#roominfo_occupants", label
= "Number of occupants" });
366 event
.formdata
["muc#roominfo_occupants"] = tostring(iterators
.count(event
.room
:each_occupant()));
369 function room_mt
:get_disco_items(stanza
) -- luacheck: ignore 212
370 return st
.reply(stanza
):query("http://jabber.org/protocol/disco#items");
373 function room_mt
:handle_kickable(origin
, stanza
) -- luacheck: ignore 212
374 local real_jid
= stanza
.attr
.from
;
375 local occupant
= self
:get_occupant_by_real_jid(real_jid
);
376 if occupant
== nil then return nil; end
377 local type, condition
, text
= stanza
:get_error();
378 local error_message
= "Kicked: "..(condition
and condition
:gsub("%-", " ") or "presence error");
379 if text
and self
:get_whois() == "anyone" then
380 error_message
= error_message
..": "..text
;
382 occupant
:set_session(real_jid
, st
.presence({type="unavailable"})
383 :tag('status'):text(error_message
));
384 local is_last_session
= occupant
.jid
== real_jid
;
385 if is_last_session
then
388 local new_occupant
= self
:save_occupant(occupant
);
389 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
390 if is_last_session
then
391 x
:tag("status", {code
= "333"});
393 self
:publicise_occupant_status(new_occupant
or occupant
, x
);
394 if is_last_session
then
395 module
:fire_event("muc-occupant-left", {
397 nick
= occupant
.nick
;
404 -- Give the room creator owner affiliation
405 module
:hook("muc-room-pre-create", function(event
)
406 event
.room
:set_affiliation(true, jid_bare(event
.stanza
.attr
.from
), "owner");
409 -- check if user is banned
410 module
:hook("muc-occupant-pre-join", function(event
)
411 local room
, stanza
= event
.room
, event
.stanza
;
412 local affiliation
= room
:get_affiliation(stanza
.attr
.from
);
413 if affiliation
== "outcast" then
414 local reply
= st
.error_reply(stanza
, "auth", "forbidden"):up();
415 reply
.tags
[1].attr
.code
= "403";
416 event
.origin
.send(reply
:tag("x", {xmlns
= "http://jabber.org/protocol/muc"}));
421 module
:hook("muc-occupant-pre-join", function(event
)
422 local nick
= jid_resource(event
.occupant
.nick
);
423 if not nick
:find("%S") then
424 event
.origin
.send(st
.error_reply(event
.stanza
, "modify", "not-allowed", "Invisible Nicknames are forbidden"));
429 module
:hook("muc-occupant-pre-change", function(event
)
430 if not jid_resource(event
.dest_occupant
.nick
):find("%S") then
431 event
.origin
.send(st
.error_reply(event
.stanza
, "modify", "not-allowed", "Invisible Nicknames are forbidden"));
436 function room_mt
:handle_first_presence(origin
, stanza
)
437 local real_jid
= stanza
.attr
.from
;
438 local dest_jid
= stanza
.attr
.to
;
439 local bare_jid
= jid_bare(real_jid
);
440 if module
:fire_event("muc-room-pre-create", {
444 }) then return true; end
445 local is_first_dest_session
= true;
446 local dest_occupant
= self
:new_occupant(bare_jid
, dest_jid
);
448 local orig_nick
= dest_occupant
.nick
;
449 if module
:fire_event("muc-occupant-pre-join", {
453 is_first_session
= is_first_dest_session
;
455 occupant
= dest_occupant
;
456 }) then return true; end
457 local nick_changed
= orig_nick
~= dest_occupant
.nick
;
459 dest_occupant
:set_session(real_jid
, stanza
);
460 local dest_x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
461 dest_x
:tag("status", {code
= "201"}):up();
462 if self
:get_whois() == "anyone" then
463 dest_x
:tag("status", {code
= "100"}):up();
466 dest_x
:tag("status", {code
= "210"}):up();
468 self
:save_occupant(dest_occupant
);
470 self
:publicise_occupant_status(dest_occupant
, dest_x
);
472 module
:fire_event("muc-occupant-joined", {
474 nick
= dest_occupant
.nick
;
475 occupant
= dest_occupant
;
479 module
:fire_event("muc-occupant-session-new", {
481 nick
= dest_occupant
.nick
;
482 occupant
= dest_occupant
;
487 module
:fire_event("muc-room-created", {
489 creator
= dest_occupant
;
496 function room_mt
:handle_normal_presence(origin
, stanza
)
497 local type = stanza
.attr
.type;
498 local real_jid
= stanza
.attr
.from
;
499 local bare_jid
= jid_bare(real_jid
);
500 local orig_occupant
= self
:get_occupant_by_real_jid(real_jid
);
501 local muc_x
= stanza
:get_child("x", "http://jabber.org/protocol/muc");
503 if orig_occupant
== nil and not muc_x
and stanza
.attr
.type == nil then
504 module
:log("debug", "Attempted join without <x>, possibly desynced");
505 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found",
506 "You are not currently connected to this chat"));
510 local is_first_dest_session
;
512 if type == "unavailable" then
513 if orig_occupant
== nil then return true; end -- Unavailable from someone not in the room
514 -- dest_occupant = nil
515 elseif orig_occupant
and orig_occupant
.nick
== stanza
.attr
.to
then -- Just a presence update
516 log("debug", "presence update for %s from session %s", orig_occupant
.nick
, real_jid
);
517 dest_occupant
= orig_occupant
;
519 local dest_jid
= stanza
.attr
.to
;
520 dest_occupant
= self
:get_occupant_by_nick(dest_jid
);
521 if dest_occupant
== nil then
522 log("debug", "no occupant found for %s; creating new occupant object for %s", dest_jid
, real_jid
);
523 is_first_dest_session
= true;
524 dest_occupant
= self
:new_occupant(bare_jid
, dest_jid
);
526 is_first_dest_session
= false;
529 local is_last_orig_session
;
530 if orig_occupant
~= nil then
531 -- Is there are least 2 sessions?
532 local iter
, ob
, last
= orig_occupant
:each_session();
533 is_last_orig_session
= iter(ob
, iter(ob
, last
)) == nil;
536 local orig_nick
= dest_occupant
and dest_occupant
.nick
;
538 local event
, event_name
= {
542 is_first_session
= is_first_dest_session
;
543 is_last_session
= is_last_orig_session
;
545 if orig_occupant
== nil then
546 event_name
= "muc-occupant-pre-join";
547 event
.occupant
= dest_occupant
;
548 elseif dest_occupant
== nil then
549 event_name
= "muc-occupant-pre-leave";
550 event
.occupant
= orig_occupant
;
552 event_name
= "muc-occupant-pre-change";
553 event
.orig_occupant
= orig_occupant
;
554 event
.dest_occupant
= dest_occupant
;
556 if module
:fire_event(event_name
, event
) then return true; end
558 local nick_changed
= dest_occupant
and orig_nick
~= dest_occupant
.nick
;
560 -- Check for nick conflicts
561 if dest_occupant
~= nil and not is_first_dest_session
562 and bare_jid
~= jid_bare(dest_occupant
.bare_jid
) then
563 -- new nick or has different bare real jid
564 log("debug", "%s couldn't join due to nick conflict: %s", real_jid
, dest_occupant
.nick
);
565 local reply
= st
.error_reply(stanza
, "cancel", "conflict"):up();
566 reply
.tags
[1].attr
.code
= "409";
567 origin
.send(reply
:tag("x", {xmlns
= "http://jabber.org/protocol/muc"}));
571 -- Send presence stanza about original occupant
572 if orig_occupant
~= nil and orig_occupant
~= dest_occupant
then
573 local orig_x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
575 if dest_occupant
== nil then -- Session is leaving
576 log("debug", "session %s is leaving occupant %s", real_jid
, orig_occupant
.nick
);
577 if is_last_orig_session
then
578 orig_occupant
.role
= nil;
580 orig_occupant
:set_session(real_jid
, stanza
);
582 log("debug", "session %s is changing from occupant %s to %s", real_jid
, orig_occupant
.nick
, dest_occupant
.nick
);
583 local generated_unavail
= st
.presence
{from
= orig_occupant
.nick
, to
= real_jid
, type = "unavailable"};
584 orig_occupant
:set_session(real_jid
, generated_unavail
);
585 dest_nick
= jid_resource(dest_occupant
.nick
);
586 if not is_first_dest_session
then -- User is swapping into another pre-existing session
587 log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid
, dest_occupant
.nick
);
588 -- Show the other session leaving
589 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
590 add_item(x
, self
:get_affiliation(bare_jid
), "none");
591 local pr
= st
.presence
{from
= dest_occupant
.nick
, to
= real_jid
, type = "unavailable"}
592 :tag("status"):text("you are joining pre-existing session " .. dest_nick
):up()
594 self
:route_stanza(pr
);
596 if is_first_dest_session
and is_last_orig_session
then -- Normal nick change
597 log("debug", "no sessions in %s left; publicly marking as nick change", orig_occupant
.nick
);
598 orig_x
:tag("status", {code
= "303";}):up();
599 else -- The session itself always needs to see a nick change
600 -- don't want to get our old nick's available presence,
601 -- so remove our session from there, and manually generate an unavailable
602 orig_occupant
:remove_session(real_jid
);
603 log("debug", "generating nick change for %s", real_jid
);
604 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
605 -- COMPAT: clients get confused if they see other items besides their own
606 -- self:build_item_list(orig_occupant, x, false, dest_nick);
607 add_item(x
, self
:get_affiliation(bare_jid
), orig_occupant
.role
, real_jid
, dest_nick
);
608 x
:tag("status", {code
= "303";}):up();
609 x
:tag("status", {code
= "110";}):up();
610 self
:route_stanza(generated_unavail
:add_child(x
));
611 dest_nick
= nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant
615 self
:save_occupant(orig_occupant
);
616 self
:publicise_occupant_status(orig_occupant
, orig_x
, dest_nick
);
618 if is_last_orig_session
then
619 module
:fire_event("muc-occupant-left", {
621 nick
= orig_occupant
.nick
;
622 occupant
= orig_occupant
;
629 if dest_occupant
~= nil then
630 dest_occupant
:set_session(real_jid
, stanza
);
631 self
:save_occupant(dest_occupant
);
633 if orig_occupant
== nil or muc_x
then
634 -- Send occupant list to newly joined or desynced user
635 self
:send_occupant_list(real_jid
, function(nick
, occupant
) -- luacheck: ignore 212
636 -- Don't include self
637 return occupant
:get_presence(real_jid
) == nil;
640 local dest_x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
641 local self_x
= st
.clone(dest_x
);
642 if orig_occupant
== nil and self
:get_whois() == "anyone" then
643 self_x
:tag("status", {code
= "100"}):up();
646 self_x
:tag("status", {code
="210"}):up();
648 self
:publicise_occupant_status(dest_occupant
, {base
=dest_x
,self
=self_x
});
650 if orig_occupant
~= nil and orig_occupant
~= dest_occupant
and not is_last_orig_session
then
651 -- If user is swapping and wasn't last original session
652 log("debug", "session %s split nicks; showing %s rejoining", real_jid
, orig_occupant
.nick
);
653 -- Show the original nick joining again
654 local pr
= st
.clone(orig_occupant
:get_presence());
655 pr
.attr
.to
= real_jid
;
656 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user";});
657 self
:build_item_list(orig_occupant
, x
, false);
658 -- TODO: new status code to inform client this was the multi-session it left?
660 self
:route_stanza(pr
);
663 if orig_occupant
== nil or muc_x
then
664 if is_first_dest_session
then
665 module
:fire_event("muc-occupant-joined", {
667 nick
= dest_occupant
.nick
;
668 occupant
= dest_occupant
;
673 module
:fire_event("muc-occupant-session-new", {
675 nick
= dest_occupant
.nick
;
676 occupant
= dest_occupant
;
686 function room_mt
:handle_presence_to_occupant(origin
, stanza
)
687 local type = stanza
.attr
.type;
688 if type == "error" then -- error, kick em out!
689 return self
:handle_kickable(origin
, stanza
)
690 elseif type == nil or type == "unavailable" then
691 return self
:handle_normal_presence(origin
, stanza
);
692 elseif type ~= 'result' then -- bad type
693 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
694 origin
.send(st
.error_reply(stanza
, "modify", "bad-request")); -- FIXME correct error?
700 function room_mt
:handle_iq_to_occupant(origin
, stanza
)
701 local from
, to
= stanza
.attr
.from
, stanza
.attr
.to
;
702 local type = stanza
.attr
.type;
703 local id
= stanza
.attr
.id
;
704 local occupant
= self
:get_occupant_by_nick(to
);
705 if (type == "error" or type == "result") then
706 do -- deconstruct_stanza_id
707 if not occupant
then return nil; end
708 local from_jid
, orig_id
, to_jid_hash
= (base64
.decode(id
) or ""):match("^(%Z+)%z(%Z*)%z(.+)$");
709 if not(from
== from_jid
or from
== jid_bare(from_jid
)) then return nil; end
710 local from_occupant_jid
= self
:get_occupant_jid(from_jid
);
711 if from_occupant_jid
== nil then return nil; end
713 for to_jid
in occupant
:each_session() do
714 if md5(to_jid
) == to_jid_hash
then
715 session_jid
= to_jid
;
719 if session_jid
== nil then return nil; end
720 stanza
.attr
.from
, stanza
.attr
.to
, stanza
.attr
.id
= from_occupant_jid
, session_jid
, orig_id
;
722 log("debug", "%s sent private iq stanza to %s (%s)", from
, to
, stanza
.attr
.to
);
723 self
:route_stanza(stanza
);
724 stanza
.attr
.from
, stanza
.attr
.to
, stanza
.attr
.id
= from
, to
, id
;
726 else -- Type is "get" or "set"
727 local current_nick
= self
:get_occupant_jid(from
);
728 if not current_nick
then
729 origin
.send(st
.error_reply(stanza
, "cancel", "not-acceptable", "You are not currently connected to this chat"));
732 if not occupant
then -- recipient not in room
733 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Recipient not in room"));
736 -- XEP-0410 MUC Self-Ping #1220
737 if to
== current_nick
and stanza
.attr
.type == "get" and stanza
:get_child("ping", "urn:xmpp:ping") then
738 self
:route_stanza(st
.reply(stanza
));
741 do -- construct_stanza_id
742 stanza
.attr
.id
= base64
.encode(occupant
.jid
.."\0"..stanza
.attr
.id
.."\0"..md5(from
));
744 stanza
.attr
.from
, stanza
.attr
.to
= current_nick
, occupant
.jid
;
745 log("debug", "%s sent private iq stanza to %s (%s)", from
, to
, occupant
.jid
);
746 local iq_ns
= stanza
.tags
[1].attr
.xmlns
;
747 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
748 stanza
.attr
.to
= jid_bare(stanza
.attr
.to
);
750 self
:route_stanza(stanza
);
751 stanza
.attr
.from
, stanza
.attr
.to
, stanza
.attr
.id
= from
, to
, id
;
756 function room_mt
:handle_message_to_occupant(origin
, stanza
)
757 local from
, to
= stanza
.attr
.from
, stanza
.attr
.to
;
758 local current_nick
= self
:get_occupant_jid(from
);
759 local type = stanza
.attr
.type;
760 if not current_nick
then -- not in room
761 if type ~= "error" then
762 origin
.send(st
.error_reply(stanza
, "cancel", "not-acceptable", "You are not currently connected to this chat"));
766 if type == "groupchat" then -- groupchat messages not allowed in PM
767 origin
.send(st
.error_reply(stanza
, "modify", "bad-request"));
769 elseif type == "error" and is_kickable_error(stanza
) then
770 log("debug", "%s kicked from %s for sending an error message", current_nick
, self
.jid
);
771 return self
:handle_kickable(origin
, stanza
); -- send unavailable
774 local o_data
= self
:get_occupant_by_nick(to
);
776 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Recipient not in room"));
779 log("debug", "%s sent private message stanza to %s (%s)", from
, to
, o_data
.jid
);
780 stanza
:tag("x", { xmlns
= "http://jabber.org/protocol/muc#user" }):up();
781 stanza
.attr
.from
= current_nick
;
782 self
:route_to_occupant(o_data
, stanza
)
783 -- TODO: Remove x tag?
784 stanza
.attr
.from
= from
;
788 function room_mt
:send_form(origin
, stanza
)
789 origin
.send(st
.reply(stanza
):query("http://jabber.org/protocol/muc#owner")
790 :add_child(self
:get_form_layout(stanza
.attr
.from
):form())
794 function room_mt
:get_form_layout(actor
)
795 local form
= dataform
.new({
796 title
= "Configuration for "..self
.jid
,
797 instructions
= "Complete and submit this form to configure the room.",
801 value
= 'http://jabber.org/protocol/muc#roomconfig'
804 return module
:fire_event("muc-config-form", { room
= self
, actor
= actor
, form
= form
}) or form
;
807 function room_mt
:process_form(origin
, stanza
)
808 local form
= stanza
.tags
[1]:get_child("x", "jabber:x:data");
809 if form
.attr
.type == "cancel" then
810 origin
.send(st
.reply(stanza
));
811 elseif form
.attr
.type == "submit" then
812 local fields
, errors
, present
;
813 if form
.tags
[1] == nil then -- Instant room
814 fields
, present
= {}, {};
816 fields
, errors
, present
= self
:get_form_layout(stanza
.attr
.from
):data(form
);
817 if fields
.FORM_TYPE
~= "http://jabber.org/protocol/muc#roomconfig" then
818 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request", "Form is not of type room configuration"));
829 actor
= stanza
.attr
.from
;
831 function event
.update_option(name
, field
, allowed
)
832 local new
= fields
[field
];
833 if new
== nil then return; end
834 if allowed
and not allowed
[new
] then return; end
835 if new
== self
["get_"..name
](self
) then return; end
836 event
.status_codes
["104"] = true;
837 self
["set_"..name
](self
, new
);
840 module
:fire_event("muc-config-submitted", event
);
841 for submitted_field
in pairs(present
) do
842 event
.field
, event
.value
= submitted_field
, fields
[submitted_field
];
843 module
:fire_event("muc-config-submitted/"..submitted_field
, event
);
845 event
.field
, event
.value
= nil, nil;
848 origin
.send(st
.reply(stanza
));
850 if next(event
.status_codes
) then
851 local msg
= st
.message({type='groupchat', from
=self
.jid
})
852 :tag('x', {xmlns
='http://jabber.org/protocol/muc#user'})
853 for code
in pairs(event
.status_codes
) do
854 msg
:tag("status", {code
= code
;}):up();
857 self
:broadcast_message(msg
);
860 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request", "Not a submitted form"));
865 -- Removes everyone from the room
866 function room_mt
:clear(x
)
867 x
= x
or st
.stanza("x", {xmlns
='http://jabber.org/protocol/muc#user'});
868 local occupants_updated
= {};
869 for nick
, occupant
in self
:each_occupant() do -- luacheck: ignore 213
871 self
:save_occupant(occupant
);
872 occupants_updated
[occupant
] = true;
874 for occupant
in pairs(occupants_updated
) do
875 self
:publicise_occupant_status(occupant
, x
);
876 module
:fire_event("muc-occupant-left", {
878 nick
= occupant
.nick
;
884 function room_mt
:destroy(newjid
, reason
, password
)
885 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user"})
886 :tag("destroy", {jid
=newjid
});
887 if reason
then x
:tag("reason"):text(reason
):up(); end
888 if password
then x
:tag("password"):text(password
):up(); end
890 self
.destroying
= reason
or true;
892 module
:fire_event("muc-room-destroyed", { room
= self
, reason
= reason
, newjid
= newjid
, password
= password
});
896 function room_mt
:handle_disco_info_get_query(origin
, stanza
)
897 origin
.send(self
:get_disco_info(stanza
));
901 function room_mt
:handle_disco_items_get_query(origin
, stanza
)
902 origin
.send(self
:get_disco_items(stanza
));
906 function room_mt
:handle_admin_query_set_command(origin
, stanza
)
907 local item
= stanza
.tags
[1].tags
[1];
909 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request"));
912 if item
.attr
.jid
then -- Validate provided JID
913 item
.attr
.jid
= jid_prep(item
.attr
.jid
);
914 if not item
.attr
.jid
then
915 origin
.send(st
.error_reply(stanza
, "modify", "jid-malformed"));
919 if item
.attr
.nick
then -- Validate provided nick
920 item
.attr
.nick
= resourceprep(item
.attr
.nick
);
921 if not item
.attr
.nick
then
922 origin
.send(st
.error_reply(stanza
, "modify", "jid-malformed", "invalid nickname"));
926 if not item
.attr
.jid
and item
.attr
.nick
then
927 -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
928 local occupant
= self
:get_occupant_by_nick(self
.jid
.."/"..item
.attr
.nick
);
929 if occupant
then item
.attr
.jid
= occupant
.bare_jid
; end
930 elseif item
.attr
.role
and not item
.attr
.nick
and item
.attr
.jid
then
931 -- Role changes should use nick, but we have a JID so pull the nick from that
932 local nick
= self
:get_occupant_jid(item
.attr
.jid
);
933 if nick
then item
.attr
.nick
= jid_resource(nick
); end
935 local actor
= stanza
.attr
.from
;
936 local reason
= item
:get_child_text("reason");
937 local success
, errtype
, err
938 if item
.attr
.affiliation
and item
.attr
.jid
and not item
.attr
.role
then
939 local registration_data
;
940 if item
.attr
.nick
then
941 local room_nick
= self
.jid
.."/"..item
.attr
.nick
;
942 local existing_occupant
= self
:get_occupant_by_nick(room_nick
);
943 if existing_occupant
and existing_occupant
.bare_jid
~= item
.attr
.jid
then
944 module
:log("debug", "Existing occupant for %s: %s does not match %s", room_nick
, existing_occupant
.bare_jid
, item
.attr
.jid
);
945 self
:set_role(true, room_nick
, nil, "This nickname is reserved");
947 module
:log("debug", "Reserving %s for %s (%s)", item
.attr
.nick
, item
.attr
.jid
, item
.attr
.affiliation
);
948 registration_data
= { reserved_nickname
= item
.attr
.nick
};
950 success
, errtype
, err
= self
:set_affiliation(actor
, item
.attr
.jid
, item
.attr
.affiliation
, reason
, registration_data
);
951 elseif item
.attr
.role
and item
.attr
.nick
and not item
.attr
.affiliation
then
952 success
, errtype
, err
= self
:set_role(actor
, self
.jid
.."/"..item
.attr
.nick
, item
.attr
.role
, reason
);
954 success
, errtype
, err
= nil, "cancel", "bad-request";
958 origin
.send(st
.error_reply(stanza
, errtype
, err
));
960 origin
.send(st
.reply(stanza
));
965 function room_mt
:handle_admin_query_get_command(origin
, stanza
)
966 local actor
= stanza
.attr
.from
;
967 local affiliation
= self
:get_affiliation(actor
);
968 local item
= stanza
.tags
[1].tags
[1];
969 local _aff
= item
.attr
.affiliation
;
970 local _aff_rank
= valid_affiliations
[_aff
or "none"];
971 local _rol
= item
.attr
.role
;
972 if _aff
and _aff_rank
and not _rol
then
973 -- You need to be at least an admin, and be requesting info about your affiliation or lower
974 -- e.g. an admin can't ask for a list of owners
975 local affiliation_rank
= valid_affiliations
[affiliation
or "none"];
976 if (affiliation_rank
>= valid_affiliations
.admin
and affiliation_rank
>= _aff_rank
)
977 or (self
:get_whois() == "anyone") then
978 local reply
= st
.reply(stanza
):query("http://jabber.org/protocol/muc#admin");
979 for jid
in self
:each_affiliation(_aff
or "none") do
980 local nick
= self
:get_registered_nick(jid
);
981 reply
:tag("item", {affiliation
= _aff
, jid
= jid
, nick
= nick
}):up();
983 origin
.send(reply
:up());
986 origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
989 elseif _rol
and valid_roles
[_rol
or "none"] and not _aff
then
990 local role
= self
:get_role(self
:get_occupant_jid(actor
)) or self
:get_default_role(affiliation
);
991 if valid_roles
[role
or "none"] >= valid_roles
.moderator
then
992 if _rol
== "none" then _rol
= nil; end
993 local reply
= st
.reply(stanza
):query("http://jabber.org/protocol/muc#admin");
994 -- TODO: whois check here? (though fully anonymous rooms are not supported)
995 for occupant_jid
, occupant
in self
:each_occupant() do
996 if occupant
.role
== _rol
then
997 local nick
= jid_resource(occupant_jid
);
998 self
:build_item_list(occupant
, reply
, false, nick
);
1001 origin
.send(reply
:up());
1004 origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
1008 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request"));
1013 function room_mt
:handle_owner_query_get_to_room(origin
, stanza
)
1014 if self
:get_affiliation(stanza
.attr
.from
) ~= "owner" then
1015 origin
.send(st
.error_reply(stanza
, "auth", "forbidden", "Only owners can configure rooms"));
1019 self
:send_form(origin
, stanza
);
1022 function room_mt
:handle_owner_query_set_to_room(origin
, stanza
)
1023 if self
:get_affiliation(stanza
.attr
.from
) ~= "owner" then
1024 origin
.send(st
.error_reply(stanza
, "auth", "forbidden", "Only owners can configure rooms"));
1028 local child
= stanza
.tags
[1].tags
[1];
1030 origin
.send(st
.error_reply(stanza
, "modify", "bad-request"));
1032 elseif child
.name
== "destroy" then
1033 local newjid
= child
.attr
.jid
;
1034 local reason
= child
:get_child_text("reason");
1035 local password
= child
:get_child_text("password");
1036 self
:destroy(newjid
, reason
, password
);
1037 origin
.send(st
.reply(stanza
));
1039 elseif child
.name
== "x" and child
.attr
.xmlns
== "jabber:x:data" then
1040 return self
:process_form(origin
, stanza
);
1042 origin
.send(st
.error_reply(stanza
, "cancel", "service-unavailable"));
1047 function room_mt
:handle_groupchat_to_room(origin
, stanza
)
1048 local from
= stanza
.attr
.from
;
1049 local occupant
= self
:get_occupant_by_real_jid(from
);
1050 if not stanza
.attr
.id
then
1051 stanza
.attr
.id
= new_id()
1053 if module
:fire_event("muc-occupant-groupchat", {
1054 room
= self
; origin
= origin
; stanza
= stanza
; from
= from
; occupant
= occupant
;
1055 }) then return true; end
1056 stanza
.attr
.from
= occupant
.nick
;
1057 self
:broadcast_message(stanza
);
1058 stanza
.attr
.from
= from
;
1063 module
:hook("muc-occupant-groupchat", function(event
)
1064 local role_rank
= valid_roles
[event
.occupant
and event
.occupant
.role
or "none"];
1065 if role_rank
<= valid_roles
.none
then
1066 event
.origin
.send(st
.error_reply(event
.stanza
, "cancel", "not-acceptable", "You are not currently connected to this chat"));
1068 elseif role_rank
<= valid_roles
.visitor
then
1069 event
.origin
.send(st
.error_reply(event
.stanza
, "auth", "forbidden"));
1074 -- hack - some buggy clients send presence updates to the room rather than their nick
1075 function room_mt
:handle_presence_to_room(origin
, stanza
)
1076 local current_nick
= self
:get_occupant_jid(stanza
.attr
.from
);
1078 if current_nick
then
1079 local to
= stanza
.attr
.to
;
1080 stanza
.attr
.to
= current_nick
;
1081 handled
= self
:handle_presence_to_occupant(origin
, stanza
);
1082 stanza
.attr
.to
= to
;
1087 -- Need visitor role or higher to invite
1088 module
:hook("muc-pre-invite", function(event
)
1089 local room
, stanza
= event
.room
, event
.stanza
;
1090 local _from
= stanza
.attr
.from
;
1091 local inviter
= room
:get_occupant_by_real_jid(_from
);
1092 local role
= inviter
and inviter
.role
or room
:get_default_role(room
:get_affiliation(_from
));
1093 if valid_roles
[role
or "none"] <= valid_roles
.visitor
then
1094 event
.origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
1099 function room_mt
:handle_mediated_invite(origin
, stanza
)
1100 local payload
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite");
1101 local invitee
= jid_prep(payload
.attr
.to
);
1103 origin
.send(st
.error_reply(stanza
, "cancel", "jid-malformed"));
1105 elseif module
:fire_event("muc-pre-invite", {room
= self
, origin
= origin
, stanza
= stanza
}) then
1108 local invite
= muc_util
.filter_muc_x(st
.clone(stanza
));
1109 invite
.attr
.from
= self
.jid
;
1110 invite
.attr
.to
= invitee
;
1111 invite
:tag('x', {xmlns
='http://jabber.org/protocol/muc#user'})
1112 :tag('invite', {from
= stanza
.attr
.from
;})
1113 :tag('reason'):text(payload
:get_child_text("reason")):up()
1116 if not module
:fire_event("muc-invite", {room
= self
, stanza
= invite
, origin
= origin
, incoming
= stanza
}) then
1117 self
:route_stanza(invite
);
1122 -- COMPAT: Some older clients expect this
1123 module
:hook("muc-invite", function(event
)
1124 local room
, stanza
= event
.room
, event
.stanza
;
1125 local invite
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite");
1126 local reason
= invite
:get_child_text("reason");
1127 stanza
:tag('x', {xmlns
= "jabber:x:conference"; jid
= room
.jid
;})
1132 -- Add a plain message for clients which don't support invites
1133 module
:hook("muc-invite", function(event
)
1134 local room
, stanza
= event
.room
, event
.stanza
;
1135 if not stanza
:get_child("body") then
1136 local invite
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite");
1137 local reason
= invite
:get_child_text("reason") or "";
1139 :text(invite
.attr
.from
.." invited you to the room "..room
.jid
..(reason
~= "" and (" ("..reason
..")") or ""))
1144 function room_mt
:handle_mediated_decline(origin
, stanza
)
1145 local payload
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline");
1146 local declinee
= jid_prep(payload
.attr
.to
);
1147 if not declinee
then
1148 origin
.send(st
.error_reply(stanza
, "cancel", "jid-malformed"));
1150 elseif module
:fire_event("muc-pre-decline", {room
= self
, origin
= origin
, stanza
= stanza
}) then
1153 local decline
= muc_util
.filter_muc_x(st
.clone(stanza
));
1154 decline
.attr
.from
= self
.jid
;
1155 decline
.attr
.to
= declinee
;
1156 decline
:tag("x", {xmlns
= "http://jabber.org/protocol/muc#user"})
1157 :tag("decline", {from
= stanza
.attr
.from
})
1158 :tag("reason"):text(payload
:get_child_text("reason")):up()
1161 if not module
:fire_event("muc-decline", {room
= self
, stanza
= decline
, origin
= origin
, incoming
= stanza
}) then
1162 declinee
= decline
.attr
.to
; -- re-fetch, in case event modified it
1164 if jid_bare(declinee
) == self
.jid
then -- declinee jid is already an in-room jid
1165 occupant
= self
:get_occupant_by_nick(declinee
);
1168 self
:route_to_occupant(occupant
, decline
);
1170 self
:route_stanza(decline
);
1176 -- Add a plain message for clients which don't support declines
1177 module
:hook("muc-decline", function(event
)
1178 local room
, stanza
= event
.room
, event
.stanza
;
1179 if not stanza
:get_child("body") then
1180 local decline
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline");
1181 local reason
= decline
:get_child_text("reason") or "";
1182 stanza
:body(decline
.attr
.from
.." declined your invite to the room "
1183 ..room
.jid
..(reason
~= "" and (" ("..reason
..")") or ""));
1187 function room_mt
:handle_message_to_room(origin
, stanza
)
1188 local type = stanza
.attr
.type;
1189 if type == "groupchat" then
1190 return self
:handle_groupchat_to_room(origin
, stanza
)
1191 elseif type == "error" and is_kickable_error(stanza
) then
1192 return self
:handle_kickable(origin
, stanza
)
1193 elseif type == nil or type == "normal" then
1194 local x
= stanza
:get_child("x", "http://jabber.org/protocol/muc#user");
1196 local payload
= x
.tags
[1];
1197 if payload
== nil then --luacheck: ignore 542
1199 elseif payload
.name
== "invite" and payload
.attr
.to
then
1200 return self
:handle_mediated_invite(origin
, stanza
)
1201 elseif payload
.name
== "decline" and payload
.attr
.to
then
1202 return self
:handle_mediated_decline(origin
, stanza
)
1204 origin
.send(st
.error_reply(stanza
, "cancel", "bad-request"));
1208 local form
= stanza
:get_child("x", "jabber:x:data");
1209 local form_type
= dataform
.get_type(form
);
1210 if form_type
== "http://jabber.org/protocol/muc#request" then
1211 self
:handle_role_request(origin
, stanza
, form
);
1217 function room_mt
:route_stanza(stanza
) -- luacheck: ignore 212
1218 module
:send(stanza
);
1221 function room_mt
:get_affiliation(jid
)
1222 local node
, host
, resource
= jid_split(jid
);
1223 -- Affiliations are granted, revoked, and maintained based on the user's bare JID.
1224 local bare
= node
and node
.."@"..host
or host
;
1225 local result
= self
._affiliations
[bare
];
1226 if not result
and self
._affiliations
[host
] == "outcast" then result
= "outcast"; end -- host banned
1230 -- Iterates over jid, affiliation pairs
1231 function room_mt
:each_affiliation(with_affiliation
)
1232 local _affiliations
, _affiliation_data
= self
._affiliations
, self
._affiliation_data
;
1233 return function(_
, jid
)
1235 repeat -- Iterate until we get a match
1236 jid
, affiliation
= next(_affiliations
, jid
);
1237 until with_affiliation
== nil or jid
== nil or affiliation
== with_affiliation
1238 return jid
, affiliation
, _affiliation_data
[jid
];
1242 function room_mt
:set_affiliation(actor
, jid
, affiliation
, reason
, data
)
1243 if not actor
then return nil, "modify", "not-acceptable"; end;
1245 local node
, host
, resource
= jid_split(jid
);
1246 if not host
then return nil, "modify", "not-acceptable"; end
1247 jid
= jid_join(node
, host
); -- Bare
1248 local is_host_only
= node
== nil;
1250 if valid_affiliations
[affiliation
or "none"] == nil then
1251 return nil, "modify", "not-acceptable";
1253 affiliation
= affiliation
~= "none" and affiliation
or nil; -- coerces `affiliation == false` to `nil`
1255 local target_affiliation
= self
._affiliations
[jid
]; -- Raw; don't want to check against host
1256 local is_downgrade
= valid_affiliations
[target_affiliation
or "none"] > valid_affiliations
[affiliation
or "none"];
1258 if actor
== true then
1259 actor
= nil -- So we can pass it safely to 'publicise_occupant_status' below
1261 local actor_affiliation
= self
:get_affiliation(actor
);
1262 if actor_affiliation
== "owner" then
1263 if jid_bare(actor
) == jid
and is_downgrade
then -- self change
1264 -- need at least one owner
1265 local is_last
= true;
1266 for j
in self
:each_affiliation("owner") do
1267 if j
~= jid
then is_last
= false; break; end
1270 return nil, "cancel", "conflict";
1273 -- owners can do anything else
1274 elseif affiliation
== "owner" or affiliation
== "admin"
1275 or actor_affiliation
~= "admin"
1276 or target_affiliation
== "owner" or target_affiliation
== "admin" then
1277 -- Can't demote owners or other admins
1278 return nil, "cancel", "not-allowed";
1282 -- Set in 'database'
1283 self
._affiliations
[jid
] = affiliation
;
1284 if not affiliation
or data
== false or (data
~= nil and next(data
) == nil) then
1285 module
:log("debug", "Clearing affiliation data for %s", jid
);
1286 self
._affiliation_data
[jid
] = nil;
1288 module
:log("debug", "Updating affiliation data for %s", jid
);
1289 self
._affiliation_data
[jid
] = data
;
1293 local role
= self
:get_default_role(affiliation
);
1294 local role_rank
= valid_roles
[role
or "none"];
1295 local occupants_updated
= {}; -- Filled with old roles
1296 for nick
, occupant
in self
:each_occupant() do -- luacheck: ignore 213
1297 if occupant
.bare_jid
== jid
or (
1298 -- Outcast can be by host.
1299 is_host_only
and affiliation
== "outcast" and select(2, jid_split(occupant
.bare_jid
)) == host
1301 -- need to publicize in all cases; as affiliation in <item/> has changed.
1302 occupants_updated
[occupant
] = occupant
.role
;
1303 if occupant
.role
~= role
and (
1305 valid_roles
[occupant
.role
or "none"] < role_rank
-- upgrade
1307 occupant
.role
= role
;
1308 self
:save_occupant(occupant
);
1313 -- Tell the room of the new occupant affiliations+roles
1314 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user"});
1315 if not role
then -- getting kicked
1316 if affiliation
== "outcast" then
1317 x
:tag("status", {code
="301"}):up(); -- banned
1319 x
:tag("status", {code
="321"}):up(); -- affiliation change
1322 local is_semi_anonymous
= self
:get_whois() == "moderators";
1324 if next(occupants_updated
) ~= nil then
1325 for occupant
, old_role
in pairs(occupants_updated
) do
1326 self
:publicise_occupant_status(occupant
, x
, nil, actor
, reason
);
1327 if occupant
.role
== nil then
1328 module
:fire_event("muc-occupant-left", {
1330 nick
= occupant
.nick
;
1331 occupant
= occupant
;
1333 elseif is_semi_anonymous
and
1334 (old_role
== "moderator" and occupant
.role
~= "moderator") or
1335 (old_role
~= "moderator" and occupant
.role
== "moderator") then -- Has gained or lost moderator status
1336 -- Send everyone else's presences (as jid visibility has changed)
1337 for real_jid
in occupant
:each_session() do
1338 self
:send_occupant_list(real_jid
, function(occupant_jid
, occupant
) --luacheck: ignore 212 433
1339 return occupant
.bare_jid
~= jid
;
1345 -- Announce affiliation change for a user that is not currently in the room,
1346 -- XEP-0045 (v1.31.2) example 195
1347 -- add_item(x, affiliation, role, jid, nick, actor_nick, actor_jid, reason)
1348 local announce_msg
= st
.message({ from
= self
.jid
})
1349 :add_child(add_item(st
.clone(x
), affiliation
, nil, jid
, nil, nil, nil, reason
));
1350 local min_role
= is_semi_anonymous
and "moderator" or "none";
1351 self
:broadcast(announce_msg
, muc_util
.only_with_min_role(min_role
));
1356 module
:fire_event("muc-set-affiliation", {
1360 affiliation
= affiliation
or "none";
1362 previous_affiliation
= target_affiliation
;
1363 data
= data
and data
or nil; -- coerce false to nil
1364 in_room
= next(occupants_updated
) ~= nil;
1370 function room_mt
:get_affiliation_data(jid
, key
)
1371 local data
= self
._affiliation_data
[jid
];
1372 if not data
then return nil; end
1379 function room_mt
:get_role(nick
)
1380 local occupant
= self
:get_occupant_by_nick(nick
);
1381 return occupant
and occupant
.role
or nil;
1384 function room_mt
:may_set_role(actor
, occupant
, role
)
1388 occupant
= occupant
,
1392 module
:fire_event("muc-pre-set-role", event
);
1393 if event
.allowed
~= nil then
1394 return event
.allowed
, event
.error, event
.condition
;
1397 -- Can't do anything to other owners or admins
1398 local occupant_affiliation
= self
:get_affiliation(occupant
.bare_jid
);
1399 if occupant_affiliation
== "owner" or occupant_affiliation
== "admin" then
1400 return nil, "cancel", "not-allowed";
1403 -- If you are trying to give or take moderator role you need to be an owner or admin
1404 if occupant
.role
== "moderator" or role
== "moderator" then
1405 local actor_affiliation
= self
:get_affiliation(actor
);
1406 if actor_affiliation
~= "owner" and actor_affiliation
~= "admin" then
1407 return nil, "cancel", "not-allowed";
1411 -- Need to be in the room and a moderator
1412 local actor_occupant
= self
:get_occupant_by_real_jid(actor
);
1413 if not actor_occupant
or actor_occupant
.role
~= "moderator" then
1414 return nil, "cancel", "not-allowed";
1420 function room_mt
:set_role(actor
, occupant_jid
, role
, reason
)
1421 if not actor
then return nil, "modify", "not-acceptable"; end
1423 local occupant
= self
:get_occupant_by_nick(occupant_jid
);
1424 if not occupant
then return nil, "modify", "item-not-found"; end
1426 if valid_roles
[role
or "none"] == nil then
1427 return nil, "modify", "not-acceptable";
1429 role
= role
~= "none" and role
or nil; -- coerces `role == false` to `nil`
1431 if actor
== true then
1432 actor
= nil -- So we can pass it safely to 'publicise_occupant_status' below
1434 local allowed
, err
, condition
= self
:may_set_role(actor
, occupant
, role
)
1436 return allowed
, err
, condition
;
1440 local x
= st
.stanza("x", {xmlns
= "http://jabber.org/protocol/muc#user"});
1442 x
:tag("status", {code
= "307"}):up();
1444 occupant
.role
= role
;
1445 self
:save_occupant(occupant
);
1446 self
:publicise_occupant_status(occupant
, x
, nil, actor
, reason
);
1448 module
:fire_event("muc-occupant-left", {
1450 nick
= occupant
.nick
;
1451 occupant
= occupant
;
1457 local whois
= module
:require
"muc/whois";
1458 room_mt
.get_whois
= whois
.get
;
1459 room_mt
.set_whois
= whois
.set
;
1461 local _M
= {}; -- module "muc"
1463 function _M
.new_room(jid
, config
)
1464 return setmetatable({
1468 _data
= config
or {};
1470 _affiliation_data
= {};
1474 local new_format
= module
:get_option_boolean("new_muc_storage_format", false);
1476 function room_mt
:freeze(live
)
1477 local frozen
, state
;
1483 for user
, affiliation
in pairs(self
._affiliations
) do
1484 frozen
[user
] = affiliation
;
1490 _affiliations
= self
._affiliations
;
1491 _affiliation_data
= self
._affiliation_data
;
1496 for nick
, occupant
in self
:each_occupant() do
1498 bare_jid
= occupant
.bare_jid
;
1499 role
= occupant
.role
;
1502 for jid
, presence
in occupant
:each_session() do
1503 state
[jid
] = st
.preserialize(presence
);
1506 local history
= self
._history
;
1507 if history
and history
[1] ~= nil then
1508 state
._last_message
= st
.preserialize(history
[#history
].stanza
);
1509 state
._last_message_at
= history
[#history
].timestamp
;
1512 return frozen
, state
;
1515 function _M
.restore_room(frozen
, state
)
1516 local room_jid
= frozen
._jid
or frozen
.jid
;
1517 local room
= _M
.new_room(room_jid
, frozen
._data
);
1519 if state
and state
._last_message
and state
._last_message_at
then
1521 { stanza
= st
.deserialize(state
._last_message
),
1522 timestamp
= state
._last_message_at
, },
1526 local occupants
= {};
1527 local room_name
, room_host
= jid_split(room_jid
);
1529 room
._affiliation_data
= frozen
._affiliation_data
or {};
1531 if frozen
.jid
and frozen
._affiliations
then
1532 -- Old storage format
1533 room
._affiliations
= frozen
._affiliations
;
1535 -- New storage format
1536 for jid
, data
in pairs(frozen
) do
1537 local node
, host
, resource
= jid_split(jid
);
1538 if host
:sub(1,1) ~= "_" and not resource
and type(data
) == "string" then
1539 -- bare jid: affiliation
1540 room
._affiliations
[jid
] = data
;
1544 for jid
, data
in pairs(state
or frozen
) do
1545 local node
, host
, resource
= jid_split(jid
);
1546 if node
or host
:sub(1,1) ~= "_" then
1547 if host
== room_host
and node
== room_name
and resource
and type(data
) == "table" then
1548 -- full room jid: bare real jid and role
1550 local occupant
= occupants
[nick
] or occupant_lib
.new(data
.bare_jid
, nick
);
1551 occupant
.bare_jid
= data
.bare_jid
;
1552 occupant
.role
= data
.role
;
1553 occupant
.jid
= data
.jid
; -- Primary session JID
1554 occupants
[nick
] = occupant
;
1555 elseif type(data
) == "table" and data
.name
== "presence" then
1556 -- full user jid: presence
1557 local nick
= data
.attr
.from
;
1558 local occupant
= occupants
[nick
] or occupant_lib
.new(nil, nick
);
1559 local presence
= st
.deserialize(data
);
1560 occupant
:set_session(jid
, presence
);
1561 occupants
[nick
] = occupant
;
1566 for _
, occupant
in pairs(occupants
) do
1567 room
:save_occupant(occupant
);
1573 _M
.room_mt
= room_mt
;