X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=plugins%2Fmuc%2Fmuc.lib.lua;h=1cc001bb0fc8ead4aca56c7e9420f0a0edadbf33;hb=332edec7b662736cd8acac68ebdeeae6dc7a53c2;hp=7a635e8ecd92f2c1a96726c3b1ca73aaf8e3db88;hpb=9d1e6ef9ba5a507989957e72527bf0a525b53099;p=prosody.git diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7a635e8e..1cc001bb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1,6 +1,6 @@ -- Prosody IM --- Copyright (C) 2008-2009 Matthew Wild --- Copyright (C) 2008-2009 Waqas Hussain +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. @@ -59,19 +59,12 @@ local kickable_error_conditions = { ["service-unavailable"] = true; ["malformed error"] = true; }; + local function get_error_condition(stanza) - for _, tag in ipairs(stanza.tags) do - if tag.name == "error" and (not(tag.attr.xmlns) or tag.attr.xmlns == "jabber:client") then - for _, cond in ipairs(tag.tags) do - if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then - return cond.name; - end - end - return "malformed error"; - end - end - return "malformed error"; + local _, condition = stanza:get_error(); + return condition or "malformed error"; end + local function is_kickable_error(stanza) local cond = get_error_condition(stanza); return kickable_error_conditions[cond] and cond; @@ -89,17 +82,6 @@ local function getTag(stanza, path) return getUsingPath(stanza, path); end local function getText(stanza, path) return getUsingPath(stanza, path, true); end ----------- ---[[function get_room_disco_info(room, stanza) - return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info") - :tag("identity", {category='conference', type='text', name=room._data["name"]):up() - :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply -end -function get_room_disco_items(room, stanza) - return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); -end -- TODO allow non-private rooms]] - --- - local room_mt = {}; room_mt.__index = room_mt; @@ -115,7 +97,7 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation, role=occupant.role, nick=nick}):up(); + :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none", nick=nick}):up(); if code then stanza:tag("status", {code=code}):up(); end @@ -124,23 +106,25 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) if me then stanza:tag("status", {code='110'}); stanza.attr.to = sid; - self:route_stanza(stanza); + self:_route_stanza(stanza); end end function room_mt:broadcast_message(stanza, historic) + local to = stanza.attr.to; for occupant, o_data in pairs(self._occupants) do for jid in pairs(o_data.sessions) do stanza.attr.to = jid; - self:route_stanza(stanza); + self:_route_stanza(stanza); end end + stanza.attr.to = to; if historic then -- add to history local history = self._data['history']; if not history then history = {}; self._data['history'] = history; end - -- stanza = st.clone(stanza); + stanza = st.clone(stanza); stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - t_insert(history, st.clone(st.preserialize(stanza))); + t_insert(history, st.preserialize(stanza)); while #history > history_length do t_remove(history, 1) end end end @@ -149,7 +133,7 @@ function room_mt:broadcast_except_nick(stanza, nick) if rnick ~= nick then for jid in pairs(occupant.sessions) do stanza.attr.to = jid; - self:route_stanza(stanza); + self:_route_stanza(stanza); end end end @@ -162,8 +146,8 @@ function room_mt:send_occupant_list(to) local pres = get_filtered_presence(o_data.sessions[o_data.jid]); pres.attr.to, pres.attr.from = to, occupant; pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up(); - self:route_stanza(pres); + :tag("item", {affiliation=o_data.affiliation or "none", role=o_data.role or "none"}):up(); + self:_route_stanza(pres); end end end @@ -173,20 +157,20 @@ function room_mt:send_history(to) for _, msg in ipairs(history) do msg = st.deserialize(msg); msg.attr.to=to; - self:route_stanza(msg); + self:_route_stanza(msg); end end if self._data['subject'] then - self:route_stanza(st.message({type='groupchat', from=self.jid, to=to}):tag("subject"):text(self._data['subject'])); + self:_route_stanza(st.message({type='groupchat', from=self.jid, to=to}):tag("subject"):text(self._data['subject'])); end end -local function room_get_disco_info(self, stanza) +function room_mt:get_disco_info(stanza) return st.reply(stanza):query("http://jabber.org/protocol/disco#info") :tag("identity", {category="conference", type="text"}):up() :tag("feature", {var="http://jabber.org/protocol/muc"}); end -local function room_get_disco_items(self, stanza) +function room_mt:get_disco_items(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); for room_jid in pairs(self._occupants) do reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up(); @@ -204,6 +188,16 @@ function room_mt:set_subject(current_nick, subject) return true; end +local function build_unavailable_presence_from_error(stanza) + local type, condition, text = stanza:get_error(); + local error_message = "Kicked: "..condition:gsub("%-", " "); + if text then + error_message = error_message..": "..text; + end + return st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) + :tag('status'):text(error_message); +end + function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc local from, to = stanza.attr.from, stanza.attr.to; local room = jid_bare(to); @@ -217,8 +211,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc if type == "error" then -- error, kick em out! if current_nick then log("debug", "kicking %s from %s", current_nick, room); - self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) - :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); end elseif type == "unavailable" then -- unavailable if current_nick then @@ -232,13 +225,14 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc occupant.sessions[from] = nil; pr.attr.to = from; pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation, role='none'}):up() + :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() :tag("status", {code='110'}); - self:route_stanza(pr); + self:_route_stanza(pr); if jid ~= new_jid then pr = st.clone(occupant.sessions[new_jid]) :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation, role=occupant.role}); + :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); + pr.attr.from = current_nick; self:broadcast_except_nick(pr, current_nick); end else @@ -321,8 +315,8 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc self:broadcast_presence(pr, from); else pr.attr.to = from; - self:route_stanza(pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=affiliation, role=role}):up() + self:_route_stanza(pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up() :tag("status", {code='110'})); end self:send_history(from); @@ -353,7 +347,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc end if _to then stanza.attr.to, stanza.attr.from, stanza.attr.id = _to, self._jid_nick[_nick], _id; - self:route_stanza(stanza); + self:_route_stanza(stanza); stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id; end end @@ -364,8 +358,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc origin.send(st.error_reply(stanza, "modify", "bad-request")); elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) - :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable else -- private stanza local o_data = self._occupants[to]; if o_data then @@ -378,7 +371,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc stanza.attr.to = bare; stanza.attr.id = base64.encode(jid.."\0"..id.."\0"..md5(from)); end - self:route_stanza(stanza); + self:_route_stanza(stanza); stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id; elseif type ~= "error" and type ~= "result" then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); @@ -386,61 +379,122 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc end end -function room_mt:handle_form(origin, stanza) - if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end - if stanza.attr.type == "get" then - local title = "Configuration for "..self.jid; - origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") - :tag("x", {xmlns='jabber:x:data', type='form'}) - :tag("title"):text(title):up() - :tag("instructions"):text(title):up() - :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up() - :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'}) - :tag("value"):text(self._data.persistent and "1" or "0"):up() +function room_mt:send_form(origin, stanza) + local title = "Configuration for "..self.jid; + origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") + :tag("x", {xmlns='jabber:x:data', type='form'}) + :tag("title"):text(title):up() + :tag("instructions"):text(title):up() + :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up() + :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'}) + :tag("value"):text(self._data.persistent and "1" or "0"):up() + :up() + :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'}) + :tag("value"):text(self._data.hidden and "0" or "1"):up() + :up() + :tag("field", {type='list-single', label='Who May Discover Real JIDs?', var='muc#roomconfig_whois'}) + :tag("value"):text(self._data.whois or 'moderators'):up() + :tag("option", {label = 'Moderators Only'}) + :tag("value"):text('moderators'):up() :up() - :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'}) - :tag("value"):text(self._data.hidden and "0" or "1"):up() + :tag("option", {label = 'Anyone'}) + :tag("value"):text('anyone'):up() :up() - ); - elseif stanza.attr.type == "set" then - local query = stanza.tags[1]; - local form; - for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end - if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end - if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end - if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end - local fields = {}; - for _, field in pairs(form.tags) do - if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then - fields[field.attr.var] = field.tags[1][1] or ""; - end + :up() + ); +end + +local valid_whois = { + moderators = true, + anyone = true, +} + +function room_mt:process_form(origin, stanza) + local query = stanza.tags[1]; + local form; + for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end + if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end + if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end + if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + local fields = {}; + for _, field in pairs(form.tags) do + if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then + fields[field.attr.var] = field.tags[1][1] or ""; end - if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + end + if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + + local dirty = false + + local persistent = fields['muc#roomconfig_persistentroom']; + if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true; + else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + dirty = dirty or (self._data.persistent ~= persistent) + self._data.persistent = persistent; + module:log("debug", "persistent=%s", tostring(persistent)); + + local public = fields['muc#roomconfig_publicroom']; + if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true; + else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + dirty = dirty or (self._data.hidden ~= (not public and true or nil)) + self._data.hidden = not public and true or nil; + + local whois = fields['muc#roomconfig_whois']; + if not valid_whois[whois] then + origin.send(st.error_reply(stanza, 'cancel', 'bad-request')); + return; + end + local whois_changed = self._data.whois ~= whois + self._data.whois = whois + module:log('debug', 'whois=%s', tostring(whois)) + + if self.save then self:save(true); end + origin.send(st.reply(stanza)); - local persistent = fields['muc#roomconfig_persistentroom']; - if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true; - else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end - self._data.persistent = persistent; - module:log("debug", "persistent=%s", tostring(persistent)); + if dirty or whois_changed then + local msg = st.message({type='groupchat', from=self.jid}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() - local public = fields['muc#roomconfig_publicroom']; - if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true; - else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end - self._data.hidden = not public and true or nil; + if dirty then + msg.tags[1]:tag('status', {code = '104'}) + end + if whois_changed then + local code = (whois == 'moderators') and 173 or 172 + msg.tags[1]:tag('status', {code = code}) + end - if self.save then self:save(true); end - origin.send(st.reply(stanza)); + self:broadcast_message(msg, false) end end +function room_mt:destroy(newjid, reason, password) + local pr = st.presence({type = "unavailable"}) + :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("item", { affiliation='none', role='none' }):up() + :tag("destroy", {jid=newjid}) + if reason then pr:tag("reason"):text(reason):up(); end + if password then pr:tag("password"):text(password):up(); end + for nick, occupant in pairs(self._occupants) do + pr.attr.from = nick; + for jid in pairs(occupant.sessions) do + pr.attr.to = jid; + self:_route_stanza(pr); + self._jid_nick[jid] = nil; + end + self._occupants[nick] = nil; + end + self._data.persistent = nil; + if self.save then self:save(true); end +end + function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if stanza.name == "iq" then if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" then - origin.send(room_get_disco_info(self, stanza)); + origin.send(self:get_disco_info(stanza)); elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" then - origin.send(room_get_disco_items(self, stanza)); + origin.send(self:get_disco_items(stanza)); elseif xmlns == "http://jabber.org/protocol/muc#admin" then local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); @@ -460,6 +514,9 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation local occupant = self._occupants[self.jid.."/"..item.attr.nick]; if occupant then item.attr.jid = occupant.jid; end + elseif not item.attr.nick and item.attr.jid then + local nick = self._jid_nick[item.attr.jid]; + if nick then item.attr.nick = select(3, jid_split(nick)); end end local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; if item.attr.affiliation and item.attr.jid and not item.attr.role then @@ -491,9 +548,14 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? if _rol == "none" then _rol = nil; end local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for nick, occupant in pairs(self._occupants) do + for occupant_jid, occupant in pairs(self._occupants) do if occupant.role == _rol then - reply:tag("item", {nick = nick, role = _rol or "none", affiliation = occupant.affiliation or "none", jid = occupant.jid}):up(); + reply:tag("item", { + nick = select(3, jid_split(occupant_jid)), + role = _rol or "none", + affiliation = occupant.affiliation or "none", + jid = occupant.jid + }):up(); end end origin.send(reply); @@ -508,7 +570,30 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha origin.send(st.error_reply(stanza, "cancel", "bad-request")); end elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - self:handle_form(origin, stanza); + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + elseif stanza.attr.type == "get" then + self:send_form(origin, stanza); + elseif stanza.attr.type == "set" then + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "auth", "bad-request")); + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; + end + end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + else + self:process_form(origin, stanza); + end + end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end @@ -516,23 +601,31 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha local from, to = stanza.attr.from, stanza.attr.to; local room = jid_bare(to); local current_nick = self._jid_nick[from]; - if not current_nick then -- not in room + local occupant = self._occupants[current_nick]; + if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + elseif occupant.role == "visitor" then + origin.send(st.error_reply(stanza, "cancel", "forbidden")); else local from = stanza.attr.from; stanza.attr.from = current_nick; local subject = getText(stanza, {"subject"}); if subject then - self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza + if occupant.role == "moderator" then + self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza + else + stanza.attr.from = from; + origin.send(st.error_reply(stanza, "cancel", "forbidden")); + end else self:broadcast_message(stanza, true); end + stanza.attr.from = from; end elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) - :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick local to = stanza.attr.to; local current_nick = self._jid_nick[stanza.attr.from]; @@ -564,7 +657,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - self:route_stanza(invite); + self:_route_stanza(invite); else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); end @@ -633,7 +726,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick if not role then self._jid_nick[jid] = nil; end p.attr.to = jid; - self:route_stanza(p); + self:_route_stanza(p); end end end @@ -650,21 +743,21 @@ function room_mt:get_role(nick) local session = self._occupants[nick]; return session and session.role or nil; end -function room_mt:set_role(actor, nick, role, callback, reason) +function room_mt:set_role(actor, occupant_jid, role, callback, reason) if role == "none" then role = nil; end if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end - local occupant = self._occupants[nick]; + local occupant = self._occupants[occupant_jid]; if not occupant then return nil, "modify", "not-acceptable"; end if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end - local p = st.presence({from = nick}) + local p = st.presence({from = occupant_jid}) :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", {affiliation=occupant.affiliation or "none", nick=nick, role=role or "none"}) + :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) :tag("reason"):text(reason or ""):up() :up(); if not role then -- kick p.attr.type = "unavailable"; - self._occupants[nick] = nil; + self._occupants[occupant_jid] = nil; for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick self._jid_nick[jid] = nil; end @@ -674,13 +767,52 @@ function room_mt:set_role(actor, nick, role, callback, reason) end for jid in pairs(occupant.sessions) do -- send to all sessions of the nick p.attr.to = jid; - self:route_stanza(p); + self:_route_stanza(p); end if callback then callback(); end - self:broadcast_except_nick(p, nick); + self:broadcast_except_nick(p, occupant_jid); return true; end +function room_mt:_route_stanza(stanza) + local muc_child; + local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; + local from_occupant = self._occupants[stanza.attr.from]; + if stanza.name == "presence" then + if to_occupant and from_occupant then + if self._data.whois == 'anyone' then + muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + else + if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then + muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + end + end + end + end + if muc_child then + for _, item in pairs(muc_child.tags) do + if item.name == "item" then + if from_occupant == to_occupant then + item.attr.jid = stanza.attr.to; + else + item.attr.jid = from_occupant.jid; + end + end + end + if self._data.whois == 'anyone' then + muc_child:tag('status', { code = '100' }); + end + end + self:route_stanza(stanza); + if muc_child then + for _, item in pairs(muc_child.tags) do + if item.name == "item" then + item.attr.jid = nil; + end + end + end +end + local _M = {}; -- module "muc" function _M.new_room(jid) @@ -688,7 +820,9 @@ function _M.new_room(jid) jid = jid; _jid_nick = {}; _occupants = {}; - _data = {}; + _data = { + whois = 'moderators', + }; _affiliations = {}; }, room_mt); end