X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=plugins%2Fmuc%2Fmuc.lib.lua;h=7a069852cf3e40960e69d2ddaef67315fd9e0c2c;hb=9e237b2b10201b73e512ae3a4e9129a8019592fc;hp=a3163349f2e14fdeb351081f83190f6d23970ceb;hpb=882e8b5f9add45cbaa5ec4e6adb8f156f7e5b1ab;p=prosody.git diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a3163349..7a069852 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -12,6 +12,8 @@ local pairs, ipairs = pairs, ipairs; local datamanager = require "util.datamanager"; local datetime = require "util.datetime"; +local dataform = require "util.dataforms"; + local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; @@ -111,7 +113,7 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) self:broadcast_except_nick(stanza, stanza.attr.from); local me = self._occupants[stanza.attr.from]; if me then - stanza:tag("status", {code='110'}); + stanza:tag("status", {code='110'}):up(); stanza.attr.to = sid; self:_route_stanza(stanza); end @@ -178,7 +180,7 @@ function room_mt:send_history(to, stanza) if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end local since = history_tag and history_tag.attr.since; - if since and not since:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%dZ$") then since = nil; end -- FIXME timezone support + if since then since = datetime.parse(since); since = since and datetime.datetime(since); end if seconds and (not since or since < seconds) then since = seconds; end local n = 0; @@ -220,14 +222,10 @@ function room_mt:get_disco_info(stanza) :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up() :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up() :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() - :tag("x", {xmlns="jabber:x:data", type="result"}) - :tag("field", {var="FORM_TYPE", type="hidden"}) - :tag("value"):text("http://jabber.org/protocol/muc#roominfo"):up() - :up() - :tag("field", {var="muc#roominfo_description", label="Description"}) - :tag("value"):text(self:get_description()):up() - :up() - :up() + :add_child(dataform.new({ + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, + { name = "muc#roominfo_description", label = "Description"} + }):form({["muc#roominfo_description"] = self:get_description()}, 'result')) ; end function room_mt:get_disco_items(stanza) @@ -358,7 +356,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc pr.attr.to = from; pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() - :tag("status", {code='110'}); + :tag("status", {code='110'}):up(); self:_route_stanza(pr); if jid ~= new_jid then pr = st.clone(occupant.sessions[new_jid]) @@ -451,14 +449,17 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc self._jid_nick[from] = to; self:send_occupant_list(from); pr.attr.from = to; + pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); if not is_merge then - 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 or "none", role=role or "none"}):up() - :tag("status", {code='110'})); + self:broadcast_except_nick(pr, to); + end + pr:tag("status", {code='110'}):up(); + if self._data.whois == 'anyone' then + pr:tag("status", {code='100'}):up(); end + pr.attr.to = from; + self:_route_stanza(pr); self:send_history(from, stanza); elseif not affiliation then -- registration required for entering members-only room local reply = st.error_reply(stanza, "auth", "registration-required"):up(); @@ -524,48 +525,78 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc end 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='text-single', label='Name', var='muc#roomconfig_roomname'}) - :tag("value"):text(self:get_name() or ""):up() - :up() - :tag("field", {type='text-single', label='Description', var='muc#roomconfig_roomdesc'}) - :tag("value"):text(self:get_description() or ""):up() - :up() - :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'}) - :tag("value"):text(self:is_persistent() and "1" or "0"):up() - :up() - :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'}) - :tag("value"):text(self:is_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("option", {label = 'Anyone'}) - :tag("value"):text('anyone'):up() - :up() - :up() - :tag("field", {type='text-private', label='Password', var='muc#roomconfig_roomsecret'}) - :tag("value"):text(self:get_password() or ""):up() - :up() - :tag("field", {type='boolean', label='Make Room Moderated?', var='muc#roomconfig_moderatedroom'}) - :tag("value"):text(self:is_moderated() and "1" or "0"):up() - :up() - :tag("field", {type='boolean', label='Make Room Members-Only?', var='muc#roomconfig_membersonly'}) - :tag("value"):text(self:is_members_only() and "1" or "0"):up() - :up() + :add_child(self:get_form_layout():form()) ); end +function room_mt:get_form_layout() + local title = "Configuration for "..self.jid; + return dataform.new({ + title = title, + instructions = title, + { + name = 'FORM_TYPE', + type = 'hidden', + value = 'http://jabber.org/protocol/muc#roomconfig' + }, + { + name = 'muc#roomconfig_roomname', + type = 'text-single', + label = 'Name', + value = self:get_name() or "", + }, + { + name = 'muc#roomconfig_roomdesc', + type = 'text-single', + label = 'Description', + value = self:get_description() or "", + }, + { + name = 'muc#roomconfig_persistentroom', + type = 'boolean', + label = 'Make Room Persistent?', + value = self:is_persistent() + }, + { + name = 'muc#roomconfig_publicroom', + type = 'boolean', + label = 'Make Room Publicly Searchable?', + value = not self:is_hidden() + }, + { + name = 'muc#roomconfig_whois', + type = 'list-single', + label = 'Who May Discover Real JIDs?', + value = { + { value = 'moderators', label = 'Moderators Only', default = self._data.whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = self._data.whois == 'anyone' } + } + }, + { + name = 'muc#roomconfig_roomsecret', + type = 'text-private', + label = 'Password', + value = self:get_password() or "", + }, + { + name = 'muc#roomconfig_moderatedroom', + type = 'boolean', + label = 'Make Room Moderated?', + value = self:is_moderated() + }, + { + name = 'muc#roomconfig_membersonly', + type = 'boolean', + label = 'Make Room Members-Only?', + value = self:is_members_only() + } + }); +end + local valid_whois = { - moderators = true, - anyone = true, + moderators = true, + anyone = true, } function room_mt:process_form(origin, stanza) @@ -574,53 +605,41 @@ function room_mt:process_form(origin, stanza) 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 - end - if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end + if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end + + local fields = self:get_form_layout():data(form); + if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end local dirty = false local name = fields['muc#roomconfig_roomname']; - if name then + if name ~= self:get_name() then self:set_name(name); end local description = fields['muc#roomconfig_roomdesc']; - if description then + if description ~= self:get_description() then self:set_description(description); end 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:is_persistent() ~= persistent) module:log("debug", "persistent=%s", tostring(persistent)); local moderated = fields['muc#roomconfig_moderatedroom']; - if moderated == "0" or moderated == "false" then moderated = nil; elseif moderated == "1" or moderated == "true" then moderated = true; - else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end dirty = dirty or (self:is_moderated() ~= moderated) module:log("debug", "moderated=%s", tostring(moderated)); local membersonly = fields['muc#roomconfig_membersonly']; - if membersonly == "0" or membersonly == "false" then membersonly = nil; elseif membersonly == "1" or membersonly == "true" then membersonly = true; - else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end dirty = dirty or (self:is_members_only() ~= membersonly) module:log("debug", "membersonly=%s", tostring(membersonly)); 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:is_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')); + origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'")); return; end local whois_changed = self._data.whois ~= whois @@ -628,7 +647,7 @@ function room_mt:process_form(origin, stanza) module:log('debug', 'whois=%s', whois) local password = fields['muc#roomconfig_roomsecret']; - if password then + if self:get_password() ~= password then self:set_password(password); end self:set_moderated(moderated); @@ -640,18 +659,18 @@ function room_mt:process_form(origin, stanza) origin.send(st.reply(stanza)); 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 msg = st.message({type='groupchat', from=self.jid}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() - 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 dirty then + msg.tags[1]:tag('status', {code = '104'}):up(); + end + if whois_changed then + local code = (whois == 'moderators') and "173" or "172"; + msg.tags[1]:tag('status', {code = code}):up(); + end - self:broadcast_message(msg, false) + self:broadcast_message(msg, false) end end @@ -847,6 +866,10 @@ 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(); + if self:is_members_only() and not self:get_affiliation(_invitee) then + log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); + self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) + end self:_route_stanza(invite); else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -888,15 +911,13 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) if jid_bare(actor) == jid then return nil, "cancel", "not-allowed"; end self._affiliations[jid] = affiliation; local role = self:get_default_role(affiliation); - local p = st.presence() - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("item", {affiliation=affiliation or "none", role=role or "none"}) :tag("reason"):text(reason or ""):up() :up(); - local x = p.tags[1]; - local item = x.tags[1]; + local presence_type = nil; if not role then -- getting kicked - p.attr.type = "unavailable"; + presence_type = "unavailable"; if affiliation == "outcast" then x:tag("status", {code="301"}):up(); -- banned else @@ -906,23 +927,28 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) local modified_nicks = {}; for nick, occupant in pairs(self._occupants) do if jid_bare(occupant.jid) == jid then - t_insert(modified_nicks, nick); if not role then -- getting kicked self._occupants[nick] = nil; else occupant.affiliation, occupant.role = affiliation, role; end - p.attr.from = nick; - for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick + for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick if not role then self._jid_nick[jid] = nil; end + local p = st.clone(pres); + p.attr.from = nick; + p.attr.type = presence_type; p.attr.to = jid; + p:add_child(x); self:_route_stanza(p); + if occupant.jid == jid then + modified_nicks[nick] = p; + end end end end if self.save then self:save(); end if callback then callback(); end - for _, nick in ipairs(modified_nicks) do + for nick,p in pairs(modified_nicks) do p.attr.from = nick; self:broadcast_except_nick(p, nick); end @@ -956,27 +982,37 @@ function room_mt:set_role(actor, occupant_jid, role, callback, reason) local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); if not allowed then return allowed, err_type, err_condition; end local occupant = self._occupants[occupant_jid]; - local p = st.presence({from = occupant_jid}) - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :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(); + local presence_type = nil; if not role then -- kick - p.attr.type = "unavailable"; + presence_type = "unavailable"; 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 - p:tag("status", {code = "307"}):up(); + x:tag("status", {code = "307"}):up(); else occupant.role = role; end - for jid in pairs(occupant.sessions) do -- send to all sessions of the nick + local bp; + for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick + local p = st.clone(pres); + p.attr.from = occupant_jid; + p.attr.type = presence_type; p.attr.to = jid; + p:add_child(x); self:_route_stanza(p); + if occupant.jid == jid then + bp = p; + end end if callback then callback(); end - self:broadcast_except_nick(p, occupant_jid); + if bp then + self:broadcast_except_nick(bp, occupant_jid); + end return true; end @@ -1005,9 +1041,6 @@ function room_mt:_route_stanza(stanza) 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