# HG changeset patch # User Matthew Wild # Date 1400873836 -3600 # Node ID da4c04df90e349a34842928b958853bb3a2cb4e1 # Parent f400a4cdf35206f06767f3c35f6689321ab53f18# Parent 2df9e7a67e569197238837432a7dcd3474bee7e8 Merge with daurnimator diff -r f400a4cdf352 -r da4c04df90e3 plugins/muc/history.lib.lua --- a/plugins/muc/history.lib.lua Sat May 17 18:17:34 2014 +0100 +++ b/plugins/muc/history.lib.lua Fri May 23 20:37:16 2014 +0100 @@ -11,7 +11,8 @@ local datetime = require "util.datetime"; local st = require "util.stanza"; -local default_history_length, max_history_length = 20, math.huge; +local default_history_length = 20; +local max_history_length = module:get_option_number("max_history_messages", math.huge); local function set_max_history_length(_max_history_length) max_history_length = _max_history_length or math.huge; diff -r f400a4cdf352 -r da4c04df90e3 plugins/muc/lock.lib.lua --- a/plugins/muc/lock.lib.lua Sat May 17 18:17:34 2014 +0100 +++ b/plugins/muc/lock.lib.lua Fri May 23 20:37:16 2014 +0100 @@ -23,7 +23,10 @@ end if lock_rooms then - module:hook("muc-room-created", function(event) + module:hook("muc-room-pre-create", function(event) + -- Older groupchat protocol doesn't lock + if not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then return end + -- Lock room at creation local room = event.room; lock(room); if lock_room_timeout and lock_room_timeout > 0 then @@ -33,16 +36,9 @@ end end); end - end); + end, 10); end --- Older groupchat protocol doesn't lock -module:hook("muc-room-pre-create", function(event) - if is_locked(event.room) and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then - unlock(event.room); - end -end, 10); - -- Don't let users into room while it is locked module:hook("muc-occupant-pre-join", function(event) if not event.is_new_room and is_locked(event.room) then -- Deny entry diff -r f400a4cdf352 -r da4c04df90e3 plugins/muc/mod_muc.lua --- a/plugins/muc/mod_muc.lua Sat May 17 18:17:34 2014 +0100 +++ b/plugins/muc/mod_muc.lua Fri May 23 20:37:16 2014 +0100 @@ -6,44 +6,23 @@ -- COPYING file in the source package for more information. -- -local array = require "util.array"; - if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end -local muc_host = module:get_host(); -local muc_name = module:get_option("name"); -if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end -local restrict_room_creation = module:get_option("restrict_room_creation"); -if restrict_room_creation then - if restrict_room_creation == true then - restrict_room_creation = "admin"; - elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then - restrict_room_creation = nil; - end -end - local muclib = module:require "muc"; -local muc_new_room = muclib.new_room; -local persistent = module:require "muc/persistent"; +room_mt = muclib.room_mt; -- Yes, global. +local iterators = require "util.iterators"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; -rooms = {}; -local rooms = rooms; -local persistent_rooms_storage = module:open_store("persistent"); -local persistent_rooms = persistent_rooms_storage:get() or {}; -local room_configs = module:open_store("config"); - --- Configurable options -muclib.set_max_history_length(module:get_option_number("max_history_messages")); +local rooms = module:shared "rooms"; module:depends("disco"); -module:add_identity("conference", "text", muc_name); +module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); module:add_feature("http://jabber.org/protocol/muc"); module:depends "muc_unique" module:require "muc/lock"; @@ -52,47 +31,22 @@ return um_is_admin(jid, module.host); end -room_mt = muclib.room_mt; -- Yes, global. -local _set_affiliation = room_mt.set_affiliation; -local _get_affiliation = room_mt.get_affiliation; -function muclib.room_mt:get_affiliation(jid) - if is_admin(jid) then return "owner"; end - return _get_affiliation(self, jid); -end -function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reason) - if is_admin(jid) then return nil, "modify", "not-acceptable"; end - return _set_affiliation(self, actor, jid, affiliation, callback, reason); +do -- Monkey patch to make server admins room owners + local _get_affiliation = room_mt.get_affiliation; + function room_mt:get_affiliation(jid) + if is_admin(jid) then return "owner"; end + return _get_affiliation(self, jid); + end + + local _set_affiliation = room_mt.set_affiliation; + function room_mt:set_affiliation(actor, jid, ...) + if is_admin(jid) then return nil, "modify", "not-acceptable"; end + return _set_affiliation(self, actor, jid, ...); + end end -local function room_save(room, forced) - local node = jid_split(room.jid); - local is_persistent = persistent.get(room); - persistent_rooms[room.jid] = is_persistent; - if is_persistent then - local history = room._data.history; - room._data.history = nil; - local data = { - jid = room.jid; - _data = room._data; - _affiliations = room._affiliations; - }; - room_configs:set(node, data); - room._data.history = history; - elseif forced then - room_configs:set(node, nil); - if not next(room._occupants) then -- Room empty - rooms[room.jid] = nil; - end - end - if forced then persistent_rooms_storage:set(nil, persistent_rooms); end -end - -function create_room(jid) - local room = muc_new_room(jid); - room.save = room_save; - rooms[jid] = room; - module:fire_event("muc-room-created", { room = room }); - return room; +function track_room(room) + rooms[room.jid] = room; end function forget_room(jid) @@ -103,68 +57,108 @@ return rooms[room_jid] end -local persistent_errors = false; -for jid in pairs(persistent_rooms) do - local node = jid_split(jid); - local data = room_configs:get(node); - if data then - local room = create_room(jid); - room._data = data._data; - room._affiliations = data._affiliations; - else -- missing room data - persistent_rooms[jid] = nil; - module:log("error", "Missing data for room '%s', removing from persistent room list", jid); - persistent_errors = true; +function each_room() + return iterators.values(rooms); +end + +do -- Persistent rooms + local persistent = module:require "muc/persistent"; + local persistent_rooms_storage = module:open_store("persistent"); + local persistent_rooms = persistent_rooms_storage:get() or {}; + local room_configs = module:open_store("config"); + + local function room_save(room, forced) + local node = jid_split(room.jid); + local is_persistent = persistent.get(room); + persistent_rooms[room.jid] = is_persistent; + if is_persistent then + local history = room._data.history; + room._data.history = nil; + local data = { + jid = room.jid; + _data = room._data; + _affiliations = room._affiliations; + }; + room_configs:set(node, data); + room._data.history = history; + elseif forced then + room_configs:set(node, nil); + if not next(room._occupants) then -- Room empty + rooms[room.jid] = nil; + end + end + if forced then persistent_rooms_storage:set(nil, persistent_rooms); end end -end -if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end + + -- When room is created, over-ride 'save' method + module:hook("muc-occupant-pre-create", function(event) + event.room.save = room_save; + end, 1000); + + -- Automatically destroy empty non-persistent rooms + module:hook("muc-occupant-left",function(event) + local room = event.room + if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); + end + end); -local host_room = muc_new_room(muc_host); -host_room.save = room_save; -rooms[muc_host] = host_room; + local persistent_errors = false; + for jid in pairs(persistent_rooms) do + local node = jid_split(jid); + local data = room_configs:get(node); + if data then + local room = muclib.new_room(jid); + room._data = data._data; + room._affiliations = data._affiliations; + track_room(room); + else -- missing room data + persistent_rooms[jid] = nil; + module:log("error", "Missing data for room '%s', removing from persistent room list", jid); + persistent_errors = true; + end + end + if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end +end module:hook("host-disco-items", function(event) local reply = event.reply; module:log("debug", "host-disco-items called"); - for jid, room in pairs(rooms) do + for room in each_room() do if not room:get_hidden() then - reply:tag("item", {jid=jid, name=room:get_name()}):up(); + reply:tag("item", {jid=room.jid, name=room:get_name()}):up(); end end end); +module:hook("muc-room-pre-create", function(event) + track_room(event.room); +end, -1000); + module:hook("muc-room-destroyed",function(event) local room = event.room forget_room(room.jid) end) -module:hook("muc-occupant-left",function(event) - local room = event.room - if not next(room._occupants) and not persistent.get(room) then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); +do + local restrict_room_creation = module:get_option("restrict_room_creation"); + if restrict_room_creation == true then + restrict_room_creation = "admin"; end -end); - --- Watch presence to create rooms -local function attempt_room_creation(event) - local origin, stanza = event.origin, event.stanza; - local room_jid = jid_bare(stanza.attr.to); - if stanza.attr.type == nil and - get_room_from_jid(room_jid) == nil and - ( - not(restrict_room_creation) or - is_admin(stanza.attr.from) or - ( + if restrict_room_creation then + local host_suffix = module.host:gsub("^[^%.]+%.", ""); + module:hook("muc-room-pre-create", function(event) + local user_jid = event.stanza.attr.from; + if not is_admin(user_jid) and not ( restrict_room_creation == "local" and - select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "") - ) - ) then - create_room(room_jid); + select(2, jid_split(user_jid)) == host_suffix + ) then + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end + end); end end -module:hook("presence/full", attempt_room_creation, -1) -module:hook("presence/bare", attempt_room_creation, -1) -module:hook("presence/host", attempt_room_creation, -1) for event_name, method in pairs { -- Normal room interactions @@ -192,78 +186,62 @@ } do module:hook(event_name, function (event) local origin, stanza = event.origin, event.stanza; - local room = get_room_from_jid(jid_bare(stanza.attr.to)) + local room_jid = jid_bare(stanza.attr.to); + local room = get_room_from_jid(room_jid); if room == nil then - origin.send(st.error_reply(stanza, "cancel", "not-allowed")); - return true; + -- Watch presence to create rooms + if stanza.attr.type == nil and stanza.name == "presence" then + room = muclib.new_room(room_jid); + else + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end end return room[method](room, origin, stanza); end, -2) end -hosts[module:get_host()].muc = { rooms = rooms }; - -local saved = false; -module.save = function() - saved = true; - return {rooms = rooms}; -end -module.restore = function(data) - for jid, oldroom in pairs(data.rooms or {}) do - local room = create_room(jid); - room._jid_nick = oldroom._jid_nick; - room._occupants = oldroom._occupants; - room._data = oldroom._data; - room._affiliations = oldroom._affiliations; - end - hosts[module:get_host()].muc = { rooms = rooms }; -end - function shutdown_component() - if not saved then - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("status", { code = "332"}):up(); - for roomjid, room in pairs(rooms) do - room:clear(x); - end - host_room:clear(x); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("status", { code = "332"}):up(); + for room in each_room() do + room:clear(x); end end -module.unload = shutdown_component; module:hook_global("server-stopping", shutdown_component); --- Ad-hoc commands -module:depends("adhoc") -local t_concat = table.concat; -local keys = require "util.iterators".keys; -local adhoc_new = module:require "adhoc".new; -local adhoc_initial = require "util.adhoc".new_initial_data_form; -local dataforms_new = require "util.dataforms".new; +do -- Ad-hoc commands + module:depends "adhoc"; + local t_concat = table.concat; + local adhoc_new = module:require "adhoc".new; + local adhoc_initial = require "util.adhoc".new_initial_data_form; + local array = require "util.array"; + local dataforms_new = require "util.dataforms".new; -local destroy_rooms_layout = dataforms_new { - title = "Destroy rooms"; - instructions = "Select the rooms to destroy"; + local destroy_rooms_layout = dataforms_new { + title = "Destroy rooms"; + instructions = "Select the rooms to destroy"; - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" }; - { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"}; -}; + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" }; + { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"}; + }; -local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() - return { rooms = array.collect(keys(rooms)):sort() }; -end, function(fields, errors) - if errors then - local errmsg = {}; - for name, err in pairs(errors) do - errmsg[#errmsg + 1] = name .. ": " .. err; + local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() + return { rooms = array.collect(each_room):pluck("jid"):sort(); }; + end, function(fields, errors) + if errors then + local errmsg = {}; + for name, err in pairs(errors) do + errmsg[#errmsg + 1] = name .. ": " .. err; + end + return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end - return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; - end - for _, room in ipairs(fields.rooms) do - rooms[room]:destroy(); - rooms[room] = nil; - end - return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; -end); -local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); + for _, room in ipairs(fields.rooms) do + get_room_from_jid(room):destroy(); + end + return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; + end); + local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); -module:provides("adhoc", destroy_rooms_desc); + module:provides("adhoc", destroy_rooms_desc); +end diff -r f400a4cdf352 -r da4c04df90e3 plugins/muc/muc.lib.lua --- a/plugins/muc/muc.lib.lua Sat May 17 18:17:34 2014 +0100 +++ b/plugins/muc/muc.lib.lua Fri May 23 20:37:16 2014 +0100 @@ -79,6 +79,10 @@ end end +function room_mt:has_occupant() + return next(self._occupants, nil) ~= nil +end + function room_mt:get_occupant_by_real_jid(real_jid) local occupant_jid = self:get_occupant_jid(real_jid); if occupant_jid == nil then return nil end @@ -96,11 +100,21 @@ self._jid_nick[real_jid] = nil; end end - if occupant.role ~= nil and next(occupant.sessions) then + + local has_live_session = false + if occupant.role ~= nil then for real_jid, presence in occupant:each_session() do - self._jid_nick[real_jid] = occupant.nick; + if presence.attr.type == nil then + has_live_session = true + self._jid_nick[real_jid] = occupant.nick; + end end - else + if not has_live_session then + -- Has no live sessions left; they have left the room. + occupant.role = nil + end + end + if not has_live_session then occupant = nil end self._occupants[id] = occupant @@ -109,10 +123,8 @@ function room_mt:route_to_occupant(occupant, stanza) local to = stanza.attr.to; for jid, pr in occupant:each_session() do - if pr.attr.type ~= "unavailable" then - stanza.attr.to = jid; - self:route_stanza(stanza); - end + stanza.attr.to = jid; + self:route_stanza(stanza); end stanza.attr.to = to; end @@ -396,7 +408,9 @@ local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); - orig_occupant.role = nil; + if is_last_orig_session then + orig_occupant.role = nil; + end orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); @@ -449,7 +463,7 @@ end self:save_occupant(dest_occupant); - if orig_occupant == nil and is_first_dest_session then + if orig_occupant == nil then -- Send occupant list to newly joined user self:send_occupant_list(real_jid, function(nick, occupant) -- Don't include self @@ -1110,8 +1124,6 @@ local _M = {}; -- module "muc" -_M.set_max_history_length = history.set_max_length; - function _M.new_room(jid, config) return setmetatable({ jid = jid; diff -r f400a4cdf352 -r da4c04df90e3 plugins/muc/occupant.lib.lua --- a/plugins/muc/occupant.lib.lua Sat May 17 18:17:34 2014 +0100 +++ b/plugins/muc/occupant.lib.lua Fri May 23 20:37:16 2014 +0100 @@ -53,7 +53,7 @@ -- finds another session to be the primary (there might not be one) function occupant_mt:choose_new_primary() for jid, pr in self:each_session() do - if pr.attr.type ~= "unavailable" then + if pr.attr.type == nil then return jid; end end