Software / code / prosody
Comparison
plugins/muc/muc.lib.lua @ 10434:8f709577fe8e
Merge 0.11->trunk
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Sat, 23 Nov 2019 23:12:01 +0100 |
| parent | 10431:3db8372e203c |
| parent | 10361:6e051bfca12d |
| child | 10449:2e36a54906e4 |
comparison
equal
deleted
inserted
replaced
| 10433:7777f25d5266 | 10434:8f709577fe8e |
|---|---|
| 21 local jid_resource = require "util.jid".resource; | 21 local jid_resource = require "util.jid".resource; |
| 22 local resourceprep = require "util.encodings".stringprep.resourceprep; | 22 local resourceprep = require "util.encodings".stringprep.resourceprep; |
| 23 local st = require "util.stanza"; | 23 local st = require "util.stanza"; |
| 24 local base64 = require "util.encodings".base64; | 24 local base64 = require "util.encodings".base64; |
| 25 local md5 = require "util.hashes".md5; | 25 local md5 = require "util.hashes".md5; |
| 26 local new_id = require "util.id".medium; | |
| 26 | 27 |
| 27 local log = module._log; | 28 local log = module._log; |
| 28 | 29 |
| 29 local occupant_lib = module:require "muc/occupant" | 30 local occupant_lib = module:require "muc/occupant" |
| 30 local muc_util = module:require "muc/util"; | 31 local muc_util = module:require "muc/util"; |
| 37 function room_mt:__tostring() | 38 function room_mt:__tostring() |
| 38 return "MUC room ("..self.jid..")"; | 39 return "MUC room ("..self.jid..")"; |
| 39 end | 40 end |
| 40 | 41 |
| 41 function room_mt.save() | 42 function room_mt.save() |
| 42 -- overriden by mod_muc.lua | 43 -- overridden by mod_muc.lua |
| 43 end | 44 end |
| 44 | 45 |
| 45 function room_mt:get_occupant_jid(real_jid) | 46 function room_mt:get_occupant_jid(real_jid) |
| 46 return self._jid_nick[real_jid] | 47 return self._jid_nick[real_jid] |
| 47 end | 48 end |
| 215 end | 216 end |
| 216 end | 217 end |
| 217 | 218 |
| 218 -- Broadcasts an occupant's presence to the whole room | 219 -- Broadcasts an occupant's presence to the whole room |
| 219 -- Takes the x element that goes into the stanzas | 220 -- Takes the x element that goes into the stanzas |
| 220 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason) | 221 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable) |
| 221 local base_x = x.base or x; | 222 local base_x = x.base or x; |
| 222 -- Build real jid and (optionally) occupant jid template presences | 223 -- Build real jid and (optionally) occupant jid template presences |
| 223 local base_presence do | 224 local base_presence do |
| 224 -- Try to use main jid's presence | 225 -- Try to use main jid's presence |
| 225 local pr = occupant:get_presence(); | 226 local pr = occupant:get_presence(); |
| 226 if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") then | 227 if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") and not force_unavailable then |
| 227 base_presence = st.clone(pr); | 228 base_presence = st.clone(pr); |
| 228 else -- user is leaving but didn't send a leave presence. make one for them | 229 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";}; | 230 base_presence = st.presence {from = occupant.nick; type = "unavailable";}; |
| 230 end | 231 end |
| 231 end | 232 end |
| 277 self_x = st.clone(x.self or base_x); | 278 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:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason); |
| 279 self_p = st.clone(base_presence):add_child(self_x); | 280 self_p = st.clone(base_presence):add_child(self_x); |
| 280 end | 281 end |
| 281 | 282 |
| 282 -- General populance | 283 local broadcast_roles = self:get_presence_broadcast(); |
| 284 | |
| 285 -- General populace | |
| 283 for occupant_nick, n_occupant in self:each_occupant() do | 286 for occupant_nick, n_occupant in self:each_occupant() do |
| 284 if occupant_nick ~= occupant.nick then | 287 if occupant_nick ~= occupant.nick then |
| 285 local pr; | 288 local pr; |
| 286 if can_see_real_jids(whois, n_occupant) then | 289 if can_see_real_jids(whois, n_occupant) then |
| 287 pr = get_full_p(); | 290 pr = get_full_p(); |
| 288 elseif occupant.bare_jid == n_occupant.bare_jid then | 291 elseif occupant.bare_jid == n_occupant.bare_jid then |
| 289 pr = self_p; | 292 pr = self_p; |
| 290 else | 293 else |
| 291 pr = get_anon_p(); | 294 pr = get_anon_p(); |
| 292 end | 295 end |
| 293 self:route_to_occupant(n_occupant, pr); | 296 if broadcast_roles[occupant.role or "none"] or force_unavailable then |
| 297 self:route_to_occupant(n_occupant, pr); | |
| 298 elseif prev_role and broadcast_roles[prev_role] then | |
| 299 pr.attr.type = 'unavailable'; | |
| 300 self:route_to_occupant(n_occupant, pr); | |
| 301 end | |
| 302 | |
| 294 end | 303 end |
| 295 end | 304 end |
| 296 | 305 |
| 297 -- Presences for occupant itself | 306 -- Presences for occupant itself |
| 298 self_x:tag("status", {code = "110";}):up(); | 307 self_x:tag("status", {code = "110";}):up(); |
| 312 | 321 |
| 313 function room_mt:send_occupant_list(to, filter) | 322 function room_mt:send_occupant_list(to, filter) |
| 314 local to_bare = jid_bare(to); | 323 local to_bare = jid_bare(to); |
| 315 local is_anonymous = false; | 324 local is_anonymous = false; |
| 316 local whois = self:get_whois(); | 325 local whois = self:get_whois(); |
| 326 local broadcast_roles = self:get_presence_broadcast(); | |
| 317 if whois ~= "anyone" then | 327 if whois ~= "anyone" then |
| 318 local affiliation = self:get_affiliation(to); | 328 local affiliation = self:get_affiliation(to); |
| 319 if affiliation ~= "admin" and affiliation ~= "owner" then | 329 if affiliation ~= "admin" and affiliation ~= "owner" then |
| 320 local occupant = self:get_occupant_by_real_jid(to); | 330 local occupant = self:get_occupant_by_real_jid(to); |
| 321 if not (occupant and can_see_real_jids(whois, occupant)) then | 331 if not (occupant and can_see_real_jids(whois, occupant)) then |
| 328 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); | 338 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 | 339 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()); | 340 local pres = st.clone(occupant:get_presence()); |
| 331 pres.attr.to = to; | 341 pres.attr.to = to; |
| 332 pres:add_child(x); | 342 pres:add_child(x); |
| 333 self:route_stanza(pres); | 343 if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then |
| 344 self:route_stanza(pres); | |
| 345 end | |
| 334 end | 346 end |
| 335 end | 347 end |
| 336 end | 348 end |
| 337 | 349 |
| 338 function room_mt:get_disco_info(stanza) | 350 function room_mt:get_disco_info(stanza) |
| 389 if is_last_session then | 401 if is_last_session then |
| 390 x:tag("status", {code = "333"}); | 402 x:tag("status", {code = "333"}); |
| 391 end | 403 end |
| 392 self:publicise_occupant_status(new_occupant or occupant, x); | 404 self:publicise_occupant_status(new_occupant or occupant, x); |
| 393 if is_last_session then | 405 if is_last_session then |
| 394 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); | 406 module:fire_event("muc-occupant-left", { |
| 407 room = self; | |
| 408 nick = occupant.nick; | |
| 409 occupant = occupant; | |
| 410 }); | |
| 395 end | 411 end |
| 396 return true; | 412 return true; |
| 397 end | 413 end |
| 398 | 414 |
| 399 -- Give the room creator owner affiliation | 415 -- Give the room creator owner affiliation |
| 426 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); | 442 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); |
| 427 return true; | 443 return true; |
| 428 end | 444 end |
| 429 end, 1); | 445 end, 1); |
| 430 | 446 |
| 447 module:hook("muc-occupant-pre-join", function(event) | |
| 448 local nick = jid_resource(event.occupant.nick); | |
| 449 if not resourceprep(nick, true) then -- strict | |
| 450 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation")); | |
| 451 return true; | |
| 452 end | |
| 453 end, 2); | |
| 454 | |
| 455 module:hook("muc-occupant-pre-change", function(event) | |
| 456 local nick = jid_resource(event.dest_occupant.nick); | |
| 457 if not resourceprep(nick, true) then -- strict | |
| 458 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation")); | |
| 459 return true; | |
| 460 end | |
| 461 end, 2); | |
| 462 | |
| 431 function room_mt:handle_first_presence(origin, stanza) | 463 function room_mt:handle_first_presence(origin, stanza) |
| 432 if not stanza:get_child("x", "http://jabber.org/protocol/muc") then | |
| 433 module:log("debug", "Room creation without <x>, possibly desynced"); | |
| 434 | |
| 435 origin.send(st.error_reply(stanza, "cancel", "item-not-found")); | |
| 436 return true; | |
| 437 end | |
| 438 | |
| 439 local real_jid = stanza.attr.from; | 464 local real_jid = stanza.attr.from; |
| 440 local dest_jid = stanza.attr.to; | 465 local dest_jid = stanza.attr.to; |
| 441 local bare_jid = jid_bare(real_jid); | 466 local bare_jid = jid_bare(real_jid); |
| 442 if module:fire_event("muc-room-pre-create", { | 467 if module:fire_event("muc-room-pre-create", { |
| 443 room = self; | 468 room = self; |
| 503 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); | 528 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); |
| 504 | 529 |
| 505 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then | 530 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then |
| 506 module:log("debug", "Attempted join without <x>, possibly desynced"); | 531 module:log("debug", "Attempted join without <x>, possibly desynced"); |
| 507 origin.send(st.error_reply(stanza, "cancel", "item-not-found", | 532 origin.send(st.error_reply(stanza, "cancel", "item-not-found", |
| 508 "You must join the room before sending presence updates")); | 533 "You are not currently connected to this chat")); |
| 509 return true; | 534 return true; |
| 510 end | 535 end |
| 511 | 536 |
| 512 local is_first_dest_session; | 537 local is_first_dest_session; |
| 513 local dest_occupant; | 538 local dest_occupant; |
| 611 -- self:build_item_list(orig_occupant, x, false, dest_nick); | 636 -- self:build_item_list(orig_occupant, x, false, dest_nick); |
| 612 add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick); | 637 add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick); |
| 613 x:tag("status", {code = "303";}):up(); | 638 x:tag("status", {code = "303";}):up(); |
| 614 x:tag("status", {code = "110";}):up(); | 639 x:tag("status", {code = "110";}):up(); |
| 615 self:route_stanza(generated_unavail:add_child(x)); | 640 self:route_stanza(generated_unavail:add_child(x)); |
| 616 dest_nick = nil; -- set dest_nick to nil; so general populance doesn't see it for whole orig_occupant | 641 dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant |
| 617 end | 642 end |
| 618 end | 643 end |
| 619 | 644 |
| 620 self:save_occupant(orig_occupant); | 645 self:save_occupant(orig_occupant); |
| 621 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); | 646 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); |
| 877 self:save_occupant(occupant); | 902 self:save_occupant(occupant); |
| 878 occupants_updated[occupant] = true; | 903 occupants_updated[occupant] = true; |
| 879 end | 904 end |
| 880 for occupant in pairs(occupants_updated) do | 905 for occupant in pairs(occupants_updated) do |
| 881 self:publicise_occupant_status(occupant, x); | 906 self:publicise_occupant_status(occupant, x); |
| 882 module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant;}); | 907 module:fire_event("muc-occupant-left", { |
| 908 room = self; | |
| 909 nick = occupant.nick; | |
| 910 occupant = occupant; | |
| 911 }); | |
| 883 end | 912 end |
| 884 end | 913 end |
| 885 | 914 |
| 886 function room_mt:destroy(newjid, reason, password) | 915 function room_mt:destroy(newjid, reason, password) |
| 887 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) | 916 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) |
| 970 local item = stanza.tags[1].tags[1]; | 999 local item = stanza.tags[1].tags[1]; |
| 971 local _aff = item.attr.affiliation; | 1000 local _aff = item.attr.affiliation; |
| 972 local _aff_rank = valid_affiliations[_aff or "none"]; | 1001 local _aff_rank = valid_affiliations[_aff or "none"]; |
| 973 local _rol = item.attr.role; | 1002 local _rol = item.attr.role; |
| 974 if _aff and _aff_rank and not _rol then | 1003 if _aff and _aff_rank and not _rol then |
| 975 -- You need to be at least an admin, and be requesting info about your affifiliation or lower | 1004 -- You need to be at least an admin, and be requesting info about your affiliation or lower |
| 976 -- e.g. an admin can't ask for a list of owners | 1005 -- e.g. an admin can't ask for a list of owners |
| 977 local affiliation_rank = valid_affiliations[affiliation or "none"]; | 1006 local affiliation_rank = valid_affiliations[affiliation or "none"]; |
| 978 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank) | 1007 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank) |
| 979 or (self:get_whois() == "anyone") then | 1008 or (self:get_whois() == "anyone") then |
| 980 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); | 1009 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); |
| 1047 end | 1076 end |
| 1048 | 1077 |
| 1049 function room_mt:handle_groupchat_to_room(origin, stanza) | 1078 function room_mt:handle_groupchat_to_room(origin, stanza) |
| 1050 local from = stanza.attr.from; | 1079 local from = stanza.attr.from; |
| 1051 local occupant = self:get_occupant_by_real_jid(from); | 1080 local occupant = self:get_occupant_by_real_jid(from); |
| 1081 if not stanza.attr.id then | |
| 1082 stanza.attr.id = new_id() | |
| 1083 end | |
| 1052 if module:fire_event("muc-occupant-groupchat", { | 1084 if module:fire_event("muc-occupant-groupchat", { |
| 1053 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant; | 1085 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant; |
| 1054 }) then return true; end | 1086 }) then return true; end |
| 1055 stanza.attr.from = occupant.nick; | 1087 stanza.attr.from = occupant.nick; |
| 1056 self:broadcast_message(stanza); | 1088 self:broadcast_message(stanza); |
| 1295 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 | 1327 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 |
| 1296 if occupant.bare_jid == jid or ( | 1328 if occupant.bare_jid == jid or ( |
| 1297 -- Outcast can be by host. | 1329 -- Outcast can be by host. |
| 1298 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host | 1330 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host |
| 1299 ) then | 1331 ) then |
| 1300 -- need to publcize in all cases; as affiliation in <item/> has changed. | 1332 -- need to publicize in all cases; as affiliation in <item/> has changed. |
| 1301 occupants_updated[occupant] = occupant.role; | 1333 occupants_updated[occupant] = occupant.role; |
| 1302 if occupant.role ~= role and ( | 1334 if occupant.role ~= role and ( |
| 1303 is_downgrade or | 1335 is_downgrade or |
| 1304 valid_roles[occupant.role or "none"] < role_rank -- upgrade | 1336 valid_roles[occupant.role or "none"] < role_rank -- upgrade |
| 1305 ) then | 1337 ) then |
| 1322 | 1354 |
| 1323 if next(occupants_updated) ~= nil then | 1355 if next(occupants_updated) ~= nil then |
| 1324 for occupant, old_role in pairs(occupants_updated) do | 1356 for occupant, old_role in pairs(occupants_updated) do |
| 1325 self:publicise_occupant_status(occupant, x, nil, actor, reason); | 1357 self:publicise_occupant_status(occupant, x, nil, actor, reason); |
| 1326 if occupant.role == nil then | 1358 if occupant.role == nil then |
| 1327 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); | 1359 module:fire_event("muc-occupant-left", { |
| 1360 room = self; | |
| 1361 nick = occupant.nick; | |
| 1362 occupant = occupant; | |
| 1363 }); | |
| 1328 elseif is_semi_anonymous and | 1364 elseif is_semi_anonymous and |
| 1329 (old_role == "moderator" and occupant.role ~= "moderator") or | 1365 (old_role == "moderator" and occupant.role ~= "moderator") or |
| 1330 (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status | 1366 (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status |
| 1331 -- Send everyone else's presences (as jid visibility has changed) | 1367 -- Send everyone else's presences (as jid visibility has changed) |
| 1332 for real_jid in occupant:each_session() do | 1368 for real_jid in occupant:each_session() do |
| 1374 function room_mt:get_role(nick) | 1410 function room_mt:get_role(nick) |
| 1375 local occupant = self:get_occupant_by_nick(nick); | 1411 local occupant = self:get_occupant_by_nick(nick); |
| 1376 return occupant and occupant.role or nil; | 1412 return occupant and occupant.role or nil; |
| 1377 end | 1413 end |
| 1378 | 1414 |
| 1415 function room_mt:may_set_role(actor, occupant, role) | |
| 1416 local event = { | |
| 1417 room = self, | |
| 1418 actor = actor, | |
| 1419 occupant = occupant, | |
| 1420 role = role, | |
| 1421 }; | |
| 1422 | |
| 1423 module:fire_event("muc-pre-set-role", event); | |
| 1424 if event.allowed ~= nil then | |
| 1425 return event.allowed, event.error, event.condition; | |
| 1426 end | |
| 1427 | |
| 1428 -- Can't do anything to other owners or admins | |
| 1429 local occupant_affiliation = self:get_affiliation(occupant.bare_jid); | |
| 1430 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then | |
| 1431 return nil, "cancel", "not-allowed"; | |
| 1432 end | |
| 1433 | |
| 1434 -- If you are trying to give or take moderator role you need to be an owner or admin | |
| 1435 if occupant.role == "moderator" or role == "moderator" then | |
| 1436 local actor_affiliation = self:get_affiliation(actor); | |
| 1437 if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then | |
| 1438 return nil, "cancel", "not-allowed"; | |
| 1439 end | |
| 1440 end | |
| 1441 | |
| 1442 -- Need to be in the room and a moderator | |
| 1443 local actor_occupant = self:get_occupant_by_real_jid(actor); | |
| 1444 if not actor_occupant or actor_occupant.role ~= "moderator" then | |
| 1445 return nil, "cancel", "not-allowed"; | |
| 1446 end | |
| 1447 | |
| 1448 return true; | |
| 1449 end | |
| 1450 | |
| 1379 function room_mt:set_role(actor, occupant_jid, role, reason) | 1451 function room_mt:set_role(actor, occupant_jid, role, reason) |
| 1380 if not actor then return nil, "modify", "not-acceptable"; end | 1452 if not actor then return nil, "modify", "not-acceptable"; end |
| 1381 | 1453 |
| 1382 local occupant = self:get_occupant_by_nick(occupant_jid); | 1454 local occupant = self:get_occupant_by_nick(occupant_jid); |
| 1383 if not occupant then return nil, "modify", "item-not-found"; end | 1455 if not occupant then return nil, "modify", "item-not-found"; end |
| 1388 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` | 1460 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` |
| 1389 | 1461 |
| 1390 if actor == true then | 1462 if actor == true then |
| 1391 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below | 1463 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below |
| 1392 else | 1464 else |
| 1393 -- Can't do anything to other owners or admins | 1465 local allowed, err, condition = self:may_set_role(actor, occupant, role) |
| 1394 local occupant_affiliation = self:get_affiliation(occupant.bare_jid); | 1466 if not allowed then |
| 1395 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then | 1467 return allowed, err, condition; |
| 1396 return nil, "cancel", "not-allowed"; | |
| 1397 end | |
| 1398 | |
| 1399 -- If you are trying to give or take moderator role you need to be an owner or admin | |
| 1400 if occupant.role == "moderator" or role == "moderator" then | |
| 1401 local actor_affiliation = self:get_affiliation(actor); | |
| 1402 if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then | |
| 1403 return nil, "cancel", "not-allowed"; | |
| 1404 end | |
| 1405 end | |
| 1406 | |
| 1407 -- Need to be in the room and a moderator | |
| 1408 local actor_occupant = self:get_occupant_by_real_jid(actor); | |
| 1409 if not actor_occupant or actor_occupant.role ~= "moderator" then | |
| 1410 return nil, "cancel", "not-allowed"; | |
| 1411 end | 1468 end |
| 1412 end | 1469 end |
| 1413 | 1470 |
| 1414 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); | 1471 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); |
| 1415 if not role then | 1472 if not role then |
| 1416 x:tag("status", {code = "307"}):up(); | 1473 x:tag("status", {code = "307"}):up(); |
| 1417 end | 1474 end |
| 1475 | |
| 1476 local prev_role = occupant.role; | |
| 1418 occupant.role = role; | 1477 occupant.role = role; |
| 1419 self:save_occupant(occupant); | 1478 self:save_occupant(occupant); |
| 1420 self:publicise_occupant_status(occupant, x, nil, actor, reason); | 1479 self:publicise_occupant_status(occupant, x, nil, actor, reason, prev_role); |
| 1421 if role == nil then | 1480 if role == nil then |
| 1422 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); | 1481 module:fire_event("muc-occupant-left", { |
| 1482 room = self; | |
| 1483 nick = occupant.nick; | |
| 1484 occupant = occupant; | |
| 1485 }); | |
| 1423 end | 1486 end |
| 1424 return true; | 1487 return true; |
| 1425 end | 1488 end |
| 1426 | 1489 |
| 1427 local whois = module:require "muc/whois"; | 1490 local whois = module:require "muc/whois"; |