Mercurial > prosody-hg
comparison plugins/muc/muc.lib.lua @ 6054:7a5ddbaf758d
Merge 0.9->0.10
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Wed, 02 Apr 2014 17:41:38 +0100 |
| parents | 6d410ffd6e13 09151d26560a |
| children | cb08bba0443a 428b8da1cfce |
comparison
equal
deleted
inserted
replaced
| 6053:2f93a04564b2 | 6054:7a5ddbaf758d |
|---|---|
| 1 -- Prosody IM | 1 -- Prosody IM |
| 2 -- Copyright (C) 2008-2010 Matthew Wild | 2 -- Copyright (C) 2008-2010 Matthew Wild |
| 3 -- Copyright (C) 2008-2010 Waqas Hussain | 3 -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 -- | 4 -- |
| 5 -- This project is MIT/X11 licensed. Please see the | 5 -- This project is MIT/X11 licensed. Please see the |
| 6 -- COPYING file in the source package for more information. | 6 -- COPYING file in the source package for more information. |
| 7 -- | 7 -- |
| 8 | 8 |
| 9 local select = select; | 9 local select = select; |
| 25 | 25 |
| 26 local muc_domain = nil; --module:get_host(); | 26 local muc_domain = nil; --module:get_host(); |
| 27 local default_history_length, max_history_length = 20, math.huge; | 27 local default_history_length, max_history_length = 20, math.huge; |
| 28 | 28 |
| 29 ------------ | 29 ------------ |
| 30 local function filter_xmlns_from_array(array, filters) | |
| 31 local count = 0; | |
| 32 for i=#array,1,-1 do | |
| 33 local attr = array[i].attr; | |
| 34 if filters[attr and attr.xmlns] then | |
| 35 t_remove(array, i); | |
| 36 count = count + 1; | |
| 37 end | |
| 38 end | |
| 39 return count; | |
| 40 end | |
| 41 local function filter_xmlns_from_stanza(stanza, filters) | |
| 42 if filters then | |
| 43 if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then | |
| 44 return stanza, filter_xmlns_from_array(stanza, filters); | |
| 45 end | |
| 46 end | |
| 47 return stanza, 0; | |
| 48 end | |
| 49 local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; | 30 local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; |
| 31 local function presence_filter(tag) | |
| 32 if presence_filters[tag.attr.xmlns] then | |
| 33 return nil; | |
| 34 end | |
| 35 return tag; | |
| 36 end | |
| 37 | |
| 50 local function get_filtered_presence(stanza) | 38 local function get_filtered_presence(stanza) |
| 51 return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters); | 39 return st.clone(stanza):maptags(presence_filter); |
| 52 end | 40 end |
| 53 local kickable_error_conditions = { | 41 local kickable_error_conditions = { |
| 54 ["gone"] = true; | 42 ["gone"] = true; |
| 55 ["internal-server-error"] = true; | 43 ["internal-server-error"] = true; |
| 56 ["item-not-found"] = true; | 44 ["item-not-found"] = true; |
| 70 | 58 |
| 71 local function is_kickable_error(stanza) | 59 local function is_kickable_error(stanza) |
| 72 local cond = get_error_condition(stanza); | 60 local cond = get_error_condition(stanza); |
| 73 return kickable_error_conditions[cond] and cond; | 61 return kickable_error_conditions[cond] and cond; |
| 74 end | 62 end |
| 75 local function getUsingPath(stanza, path, getText) | |
| 76 local tag = stanza; | |
| 77 for _, name in ipairs(path) do | |
| 78 if type(tag) ~= 'table' then return; end | |
| 79 tag = tag:child_with_name(name); | |
| 80 end | |
| 81 if tag and getText then tag = table.concat(tag); end | |
| 82 return tag; | |
| 83 end | |
| 84 local function getTag(stanza, path) return getUsingPath(stanza, path); end | |
| 85 local function getText(stanza, path) return getUsingPath(stanza, path, true); end | |
| 86 ----------- | 63 ----------- |
| 87 | 64 |
| 88 local room_mt = {}; | 65 local room_mt = {}; |
| 89 room_mt.__index = room_mt; | 66 room_mt.__index = room_mt; |
| 90 | 67 |
| 96 if affiliation == "owner" or affiliation == "admin" then | 73 if affiliation == "owner" or affiliation == "admin" then |
| 97 return "moderator"; | 74 return "moderator"; |
| 98 elseif affiliation == "member" then | 75 elseif affiliation == "member" then |
| 99 return "participant"; | 76 return "participant"; |
| 100 elseif not affiliation then | 77 elseif not affiliation then |
| 101 if not self:is_members_only() then | 78 if not self:get_members_only() then |
| 102 return self:is_moderated() and "visitor" or "participant"; | 79 return self:get_moderated() and "visitor" or "participant"; |
| 103 end | 80 end |
| 104 end | 81 end |
| 105 end | 82 end |
| 106 | 83 |
| 107 function room_mt:broadcast_presence(stanza, sid, code, nick) | 84 function room_mt:broadcast_presence(stanza, sid, code, nick) |
| 128 self:_route_stanza(stanza); | 105 self:_route_stanza(stanza); |
| 129 end | 106 end |
| 130 end | 107 end |
| 131 stanza.attr.to = to; | 108 stanza.attr.to = to; |
| 132 if historic then -- add to history | 109 if historic then -- add to history |
| 133 local history = self._data['history']; | 110 return self:save_to_history(stanza) |
| 134 if not history then history = {}; self._data['history'] = history; end | 111 end |
| 135 stanza = st.clone(stanza); | 112 end |
| 136 stanza.attr.to = ""; | 113 function room_mt:save_to_history(stanza) |
| 137 local stamp = datetime.datetime(); | 114 local history = self._data['history']; |
| 138 stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203 | 115 if not history then history = {}; self._data['history'] = history; end |
| 139 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) | 116 stanza = st.clone(stanza); |
| 140 local entry = { stanza = stanza, stamp = stamp }; | 117 stanza.attr.to = ""; |
| 141 t_insert(history, entry); | 118 local stamp = datetime.datetime(); |
| 142 while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end | 119 stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203 |
| 143 end | 120 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) |
| 121 local entry = { stanza = stanza, stamp = stamp }; | |
| 122 t_insert(history, entry); | |
| 123 while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end | |
| 144 end | 124 end |
| 145 function room_mt:broadcast_except_nick(stanza, nick) | 125 function room_mt:broadcast_except_nick(stanza, nick) |
| 146 for rnick, occupant in pairs(self._occupants) do | 126 for rnick, occupant in pairs(self._occupants) do |
| 147 if rnick ~= nick then | 127 if rnick ~= nick then |
| 148 for jid in pairs(occupant.sessions) do | 128 for jid in pairs(occupant.sessions) do |
| 168 function room_mt:send_history(to, stanza) | 148 function room_mt:send_history(to, stanza) |
| 169 local history = self._data['history']; -- send discussion history | 149 local history = self._data['history']; -- send discussion history |
| 170 if history then | 150 if history then |
| 171 local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc"); | 151 local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc"); |
| 172 local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); | 152 local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); |
| 173 | 153 |
| 174 local maxchars = history_tag and tonumber(history_tag.attr.maxchars); | 154 local maxchars = history_tag and tonumber(history_tag.attr.maxchars); |
| 175 if maxchars then maxchars = math.floor(maxchars); end | 155 if maxchars then maxchars = math.floor(maxchars); end |
| 176 | 156 |
| 177 local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); | 157 local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); |
| 178 if not history_tag then maxstanzas = 20; end | 158 if not history_tag then maxstanzas = 20; end |
| 179 | 159 |
| 180 local seconds = history_tag and tonumber(history_tag.attr.seconds); | 160 local seconds = history_tag and tonumber(history_tag.attr.seconds); |
| 181 if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end | 161 if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end |
| 184 if since then since = datetime.parse(since); since = since and datetime.datetime(since); end | 164 if since then since = datetime.parse(since); since = since and datetime.datetime(since); end |
| 185 if seconds and (not since or since < seconds) then since = seconds; end | 165 if seconds and (not since or since < seconds) then since = seconds; end |
| 186 | 166 |
| 187 local n = 0; | 167 local n = 0; |
| 188 local charcount = 0; | 168 local charcount = 0; |
| 189 | 169 |
| 190 for i=#history,1,-1 do | 170 for i=#history,1,-1 do |
| 191 local entry = history[i]; | 171 local entry = history[i]; |
| 192 if maxchars then | 172 if maxchars then |
| 193 if not entry.chars then | 173 if not entry.chars then |
| 194 entry.stanza.attr.to = ""; | 174 entry.stanza.attr.to = ""; |
| 205 local msg = history[i].stanza; | 185 local msg = history[i].stanza; |
| 206 msg.attr.to = to; | 186 msg.attr.to = to; |
| 207 self:_route_stanza(msg); | 187 self:_route_stanza(msg); |
| 208 end | 188 end |
| 209 end | 189 end |
| 190 end | |
| 191 function room_mt:send_subject(to) | |
| 210 if self._data['subject'] then | 192 if self._data['subject'] then |
| 211 self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject'])); | 193 self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject'])); |
| 212 end | 194 end |
| 213 end | 195 end |
| 214 | 196 |
| 216 local count = 0; for _ in pairs(self._occupants) do count = count + 1; end | 198 local count = 0; for _ in pairs(self._occupants) do count = count + 1; end |
| 217 return st.reply(stanza):query("http://jabber.org/protocol/disco#info") | 199 return st.reply(stanza):query("http://jabber.org/protocol/disco#info") |
| 218 :tag("identity", {category="conference", type="text", name=self:get_name()}):up() | 200 :tag("identity", {category="conference", type="text", name=self:get_name()}):up() |
| 219 :tag("feature", {var="http://jabber.org/protocol/muc"}):up() | 201 :tag("feature", {var="http://jabber.org/protocol/muc"}):up() |
| 220 :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up() | 202 :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up() |
| 221 :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up() | 203 :tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up() |
| 222 :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up() | 204 :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up() |
| 223 :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up() | 205 :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up() |
| 224 :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up() | 206 :tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up() |
| 225 :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() | 207 :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() |
| 226 :add_child(dataform.new({ | 208 :add_child(dataform.new({ |
| 227 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, | 209 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, |
| 228 { name = "muc#roominfo_description", label = "Description", value = "" }, | 210 { name = "muc#roominfo_description", label = "Description", value = "" }, |
| 229 { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) } | 211 { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) } |
| 236 reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up(); | 218 reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up(); |
| 237 end | 219 end |
| 238 return reply; | 220 return reply; |
| 239 end | 221 end |
| 240 function room_mt:set_subject(current_nick, subject) | 222 function room_mt:set_subject(current_nick, subject) |
| 241 -- TODO check nick's authority | |
| 242 if subject == "" then subject = nil; end | 223 if subject == "" then subject = nil; end |
| 243 self._data['subject'] = subject; | 224 self._data['subject'] = subject; |
| 244 self._data['subject_from'] = current_nick; | 225 self._data['subject_from'] = current_nick; |
| 245 if self.save then self:save(); end | 226 if self.save then self:save(); end |
| 246 local msg = st.message({type='groupchat', from=current_nick}) | 227 local msg = st.message({type='groupchat', from=current_nick}) |
| 294 if self._data.moderated ~= moderated then | 275 if self._data.moderated ~= moderated then |
| 295 self._data.moderated = moderated; | 276 self._data.moderated = moderated; |
| 296 if self.save then self:save(true); end | 277 if self.save then self:save(true); end |
| 297 end | 278 end |
| 298 end | 279 end |
| 299 function room_mt:is_moderated() | 280 function room_mt:get_moderated() |
| 300 return self._data.moderated; | 281 return self._data.moderated; |
| 301 end | 282 end |
| 302 function room_mt:set_members_only(members_only) | 283 function room_mt:set_members_only(members_only) |
| 303 members_only = members_only and true or nil; | 284 members_only = members_only and true or nil; |
| 304 if self._data.members_only ~= members_only then | 285 if self._data.members_only ~= members_only then |
| 305 self._data.members_only = members_only; | 286 self._data.members_only = members_only; |
| 306 if self.save then self:save(true); end | 287 if self.save then self:save(true); end |
| 307 end | 288 end |
| 308 end | 289 end |
| 309 function room_mt:is_members_only() | 290 function room_mt:get_members_only() |
| 310 return self._data.members_only; | 291 return self._data.members_only; |
| 311 end | 292 end |
| 312 function room_mt:set_persistent(persistent) | 293 function room_mt:set_persistent(persistent) |
| 313 persistent = persistent and true or nil; | 294 persistent = persistent and true or nil; |
| 314 if self._data.persistent ~= persistent then | 295 if self._data.persistent ~= persistent then |
| 315 self._data.persistent = persistent; | 296 self._data.persistent = persistent; |
| 316 if self.save then self:save(true); end | 297 if self.save then self:save(true); end |
| 317 end | 298 end |
| 318 end | 299 end |
| 319 function room_mt:is_persistent() | 300 function room_mt:get_persistent() |
| 320 return self._data.persistent; | 301 return self._data.persistent; |
| 321 end | 302 end |
| 322 function room_mt:set_hidden(hidden) | 303 function room_mt:set_hidden(hidden) |
| 323 hidden = hidden and true or nil; | 304 hidden = hidden and true or nil; |
| 324 if self._data.hidden ~= hidden then | 305 if self._data.hidden ~= hidden then |
| 325 self._data.hidden = hidden; | 306 self._data.hidden = hidden; |
| 326 if self.save then self:save(true); end | 307 if self.save then self:save(true); end |
| 327 end | 308 end |
| 328 end | 309 end |
| 329 function room_mt:is_hidden() | 310 function room_mt:get_hidden() |
| 330 return self._data.hidden; | 311 return self._data.hidden; |
| 312 end | |
| 313 function room_mt:get_public() | |
| 314 return not self:get_hidden(); | |
| 315 end | |
| 316 function room_mt:set_public(public) | |
| 317 return self:set_hidden(not public); | |
| 331 end | 318 end |
| 332 function room_mt:set_changesubject(changesubject) | 319 function room_mt:set_changesubject(changesubject) |
| 333 changesubject = changesubject and true or nil; | 320 changesubject = changesubject and true or nil; |
| 334 if self._data.changesubject ~= changesubject then | 321 if self._data.changesubject ~= changesubject then |
| 335 self._data.changesubject = changesubject; | 322 self._data.changesubject = changesubject; |
| 349 end | 336 end |
| 350 self._data.history_length = length; | 337 self._data.history_length = length; |
| 351 end | 338 end |
| 352 | 339 |
| 353 | 340 |
| 341 local valid_whois = { moderators = true, anyone = true }; | |
| 342 | |
| 343 function room_mt:set_whois(whois) | |
| 344 if valid_whois[whois] and self._data.whois ~= whois then | |
| 345 self._data.whois = whois; | |
| 346 if self.save then self:save(true); end | |
| 347 end | |
| 348 end | |
| 349 | |
| 350 function room_mt:get_whois() | |
| 351 return self._data.whois; | |
| 352 end | |
| 353 | |
| 354 local function construct_stanza_id(room, stanza) | 354 local function construct_stanza_id(room, stanza) |
| 355 local from_jid, to_nick = stanza.attr.from, stanza.attr.to; | 355 local from_jid, to_nick = stanza.attr.from, stanza.attr.to; |
| 356 local from_nick = room._jid_nick[from_jid]; | 356 local from_nick = room._jid_nick[from_jid]; |
| 357 local occupant = room._occupants[to_nick]; | 357 local occupant = room._occupants[to_nick]; |
| 358 local to_jid = occupant.jid; | 358 local to_jid = occupant.jid; |
| 359 | 359 |
| 360 return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid)); | 360 return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid)); |
| 361 end | 361 end |
| 362 local function deconstruct_stanza_id(room, stanza) | 362 local function deconstruct_stanza_id(room, stanza) |
| 363 local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to; | 363 local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to; |
| 364 local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); | 364 local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); |
| 483 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); | 483 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); |
| 484 else | 484 else |
| 485 log("debug", "%s joining as %s", from, to); | 485 log("debug", "%s joining as %s", from, to); |
| 486 if not next(self._affiliations) then -- new room, no owners | 486 if not next(self._affiliations) then -- new room, no owners |
| 487 self._affiliations[jid_bare(from)] = "owner"; | 487 self._affiliations[jid_bare(from)] = "owner"; |
| 488 if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then | |
| 489 self.locked = nil; -- Older groupchat protocol doesn't lock | |
| 490 end | |
| 491 elseif self.locked then -- Deny entry | |
| 492 origin.send(st.error_reply(stanza, "cancel", "item-not-found")); | |
| 493 return; | |
| 488 end | 494 end |
| 489 local affiliation = self:get_affiliation(from); | 495 local affiliation = self:get_affiliation(from); |
| 490 local role = self:get_default_role(affiliation) | 496 local role = self:get_default_role(affiliation) |
| 491 if role then -- new occupant | 497 if role then -- new occupant |
| 492 if not is_merge then | 498 if not is_merge then |
| 504 end | 510 end |
| 505 pr:tag("status", {code='110'}):up(); | 511 pr:tag("status", {code='110'}):up(); |
| 506 if self._data.whois == 'anyone' then | 512 if self._data.whois == 'anyone' then |
| 507 pr:tag("status", {code='100'}):up(); | 513 pr:tag("status", {code='100'}):up(); |
| 508 end | 514 end |
| 515 if self.locked then | |
| 516 pr:tag("status", {code='201'}):up(); | |
| 517 end | |
| 509 pr.attr.to = from; | 518 pr.attr.to = from; |
| 510 self:_route_stanza(pr); | 519 self:_route_stanza(pr); |
| 511 self:send_history(from, stanza); | 520 self:send_history(from, stanza); |
| 521 self:send_subject(from); | |
| 512 elseif not affiliation then -- registration required for entering members-only room | 522 elseif not affiliation then -- registration required for entering members-only room |
| 513 local reply = st.error_reply(stanza, "auth", "registration-required"):up(); | 523 local reply = st.error_reply(stanza, "auth", "registration-required"):up(); |
| 514 reply.tags[1].attr.code = "407"; | 524 reply.tags[1].attr.code = "407"; |
| 515 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); | 525 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); |
| 516 else -- banned | 526 else -- banned |
| 558 if stanza.attr.id then | 568 if stanza.attr.id then |
| 559 self:_route_stanza(stanza); | 569 self:_route_stanza(stanza); |
| 560 end | 570 end |
| 561 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; | 571 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; |
| 562 else -- message | 572 else -- message |
| 573 stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); | |
| 563 stanza.attr.from = current_nick; | 574 stanza.attr.from = current_nick; |
| 564 for jid in pairs(o_data.sessions) do | 575 for jid in pairs(o_data.sessions) do |
| 565 stanza.attr.to = jid; | 576 stanza.attr.to = jid; |
| 566 self:_route_stanza(stanza); | 577 self:_route_stanza(stanza); |
| 567 end | 578 end |
| 573 end | 584 end |
| 574 end | 585 end |
| 575 | 586 |
| 576 function room_mt:send_form(origin, stanza) | 587 function room_mt:send_form(origin, stanza) |
| 577 origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") | 588 origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") |
| 578 :add_child(self:get_form_layout():form()) | 589 :add_child(self:get_form_layout(stanza.attr.from):form()) |
| 579 ); | 590 ); |
| 580 end | 591 end |
| 581 | 592 |
| 582 function room_mt:get_form_layout() | 593 function room_mt:get_form_layout(actor) |
| 583 local form = dataform.new({ | 594 local form = dataform.new({ |
| 584 title = "Configuration for "..self.jid, | 595 title = "Configuration for "..self.jid, |
| 585 instructions = "Complete and submit this form to configure the room.", | 596 instructions = "Complete and submit this form to configure the room.", |
| 586 { | 597 { |
| 587 name = 'FORM_TYPE', | 598 name = 'FORM_TYPE', |
| 602 }, | 613 }, |
| 603 { | 614 { |
| 604 name = 'muc#roomconfig_persistentroom', | 615 name = 'muc#roomconfig_persistentroom', |
| 605 type = 'boolean', | 616 type = 'boolean', |
| 606 label = 'Make Room Persistent?', | 617 label = 'Make Room Persistent?', |
| 607 value = self:is_persistent() | 618 value = self:get_persistent() |
| 608 }, | 619 }, |
| 609 { | 620 { |
| 610 name = 'muc#roomconfig_publicroom', | 621 name = 'muc#roomconfig_publicroom', |
| 611 type = 'boolean', | 622 type = 'boolean', |
| 612 label = 'Make Room Publicly Searchable?', | 623 label = 'Make Room Publicly Searchable?', |
| 613 value = not self:is_hidden() | 624 value = not self:get_hidden() |
| 614 }, | 625 }, |
| 615 { | 626 { |
| 616 name = 'muc#roomconfig_changesubject', | 627 name = 'muc#roomconfig_changesubject', |
| 617 type = 'boolean', | 628 type = 'boolean', |
| 618 label = 'Allow Occupants to Change Subject?', | 629 label = 'Allow Occupants to Change Subject?', |
| 635 }, | 646 }, |
| 636 { | 647 { |
| 637 name = 'muc#roomconfig_moderatedroom', | 648 name = 'muc#roomconfig_moderatedroom', |
| 638 type = 'boolean', | 649 type = 'boolean', |
| 639 label = 'Make Room Moderated?', | 650 label = 'Make Room Moderated?', |
| 640 value = self:is_moderated() | 651 value = self:get_moderated() |
| 641 }, | 652 }, |
| 642 { | 653 { |
| 643 name = 'muc#roomconfig_membersonly', | 654 name = 'muc#roomconfig_membersonly', |
| 644 type = 'boolean', | 655 type = 'boolean', |
| 645 label = 'Make Room Members-Only?', | 656 label = 'Make Room Members-Only?', |
| 646 value = self:is_members_only() | 657 value = self:get_members_only() |
| 647 }, | 658 }, |
| 648 { | 659 { |
| 649 name = 'muc#roomconfig_historylength', | 660 name = 'muc#roomconfig_historylength', |
| 650 type = 'text-single', | 661 type = 'text-single', |
| 651 label = 'Maximum Number of History Messages Returned by Room', | 662 label = 'Maximum Number of History Messages Returned by Room', |
| 652 value = tostring(self:get_historylength()) | 663 value = tostring(self:get_historylength()) |
| 653 } | 664 } |
| 654 }); | 665 }); |
| 655 return module:fire_event("muc-config-form", { room = self, form = form }) or form; | 666 return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; |
| 656 end | 667 end |
| 657 | |
| 658 local valid_whois = { | |
| 659 moderators = true, | |
| 660 anyone = true, | |
| 661 } | |
| 662 | 668 |
| 663 function room_mt:process_form(origin, stanza) | 669 function room_mt:process_form(origin, stanza) |
| 664 local query = stanza.tags[1]; | 670 local query = stanza.tags[1]; |
| 665 local form; | 671 local form; |
| 666 for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end | 672 for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end |
| 667 if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end | 673 if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end |
| 668 if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end | 674 if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end |
| 669 if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end | 675 if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end |
| 670 | 676 |
| 671 local fields = self:get_form_layout():data(form); | 677 local fields = self:get_form_layout(stanza.attr.from):data(form); |
| 672 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 | 678 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 |
| 673 | 679 |
| 674 local dirty = false | 680 |
| 675 | 681 local changed = {}; |
| 676 local event = { room = self, fields = fields, changed = dirty }; | 682 |
| 683 local function handle_option(name, field, allowed) | |
| 684 local new = fields[field]; | |
| 685 if new == nil then return; end | |
| 686 if allowed and not allowed[new] then return; end | |
| 687 if new == self["get_"..name](self) then return; end | |
| 688 changed[name] = true; | |
| 689 self["set_"..name](self, new); | |
| 690 end | |
| 691 | |
| 692 local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; | |
| 677 module:fire_event("muc-config-submitted", event); | 693 module:fire_event("muc-config-submitted", event); |
| 678 dirty = event.changed or dirty; | 694 |
| 679 | 695 handle_option("name", "muc#roomconfig_roomname"); |
| 680 local name = fields['muc#roomconfig_roomname']; | 696 handle_option("description", "muc#roomconfig_roomdesc"); |
| 681 if name ~= self:get_name() then | 697 handle_option("persistent", "muc#roomconfig_persistentroom"); |
| 682 self:set_name(name); | 698 handle_option("moderated", "muc#roomconfig_moderatedroom"); |
| 683 end | 699 handle_option("members_only", "muc#roomconfig_membersonly"); |
| 684 | 700 handle_option("public", "muc#roomconfig_publicroom"); |
| 685 local description = fields['muc#roomconfig_roomdesc']; | 701 handle_option("changesubject", "muc#roomconfig_changesubject"); |
| 686 if description ~= self:get_description() then | 702 handle_option("historylength", "muc#roomconfig_historylength"); |
| 687 self:set_description(description); | 703 handle_option("whois", "muc#roomconfig_whois", valid_whois); |
| 688 end | 704 handle_option("password", "muc#roomconfig_roomsecret"); |
| 689 | |
| 690 local persistent = fields['muc#roomconfig_persistentroom']; | |
| 691 dirty = dirty or (self:is_persistent() ~= persistent) | |
| 692 module:log("debug", "persistent=%s", tostring(persistent)); | |
| 693 | |
| 694 local moderated = fields['muc#roomconfig_moderatedroom']; | |
| 695 dirty = dirty or (self:is_moderated() ~= moderated) | |
| 696 module:log("debug", "moderated=%s", tostring(moderated)); | |
| 697 | |
| 698 local membersonly = fields['muc#roomconfig_membersonly']; | |
| 699 dirty = dirty or (self:is_members_only() ~= membersonly) | |
| 700 module:log("debug", "membersonly=%s", tostring(membersonly)); | |
| 701 | |
| 702 local public = fields['muc#roomconfig_publicroom']; | |
| 703 dirty = dirty or (self:is_hidden() ~= (not public and true or nil)) | |
| 704 | |
| 705 local changesubject = fields['muc#roomconfig_changesubject']; | |
| 706 dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil)) | |
| 707 module:log('debug', 'changesubject=%s', changesubject and "true" or "false") | |
| 708 | |
| 709 local historylength = tonumber(fields['muc#roomconfig_historylength']); | |
| 710 dirty = dirty or (historylength and (self:get_historylength() ~= historylength)); | |
| 711 module:log('debug', 'historylength=%s', historylength) | |
| 712 | |
| 713 | |
| 714 local whois = fields['muc#roomconfig_whois']; | |
| 715 if not valid_whois[whois] then | |
| 716 origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'")); | |
| 717 return; | |
| 718 end | |
| 719 local whois_changed = self._data.whois ~= whois | |
| 720 self._data.whois = whois | |
| 721 module:log('debug', 'whois=%s', whois) | |
| 722 | |
| 723 local password = fields['muc#roomconfig_roomsecret']; | |
| 724 if self:get_password() ~= password then | |
| 725 self:set_password(password); | |
| 726 end | |
| 727 self:set_moderated(moderated); | |
| 728 self:set_members_only(membersonly); | |
| 729 self:set_persistent(persistent); | |
| 730 self:set_hidden(not public); | |
| 731 self:set_changesubject(changesubject); | |
| 732 self:set_historylength(historylength); | |
| 733 | 705 |
| 734 if self.save then self:save(true); end | 706 if self.save then self:save(true); end |
| 707 if self.locked then | |
| 708 module:fire_event("muc-room-unlocked", { room = self }); | |
| 709 self.locked = nil; | |
| 710 end | |
| 735 origin.send(st.reply(stanza)); | 711 origin.send(st.reply(stanza)); |
| 736 | 712 |
| 737 if dirty or whois_changed then | 713 if next(changed) then |
| 738 local msg = st.message({type='groupchat', from=self.jid}) | 714 local msg = st.message({type='groupchat', from=self.jid}) |
| 739 :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() | 715 :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() |
| 740 | 716 :tag('status', {code = '104'}):up(); |
| 741 if dirty then | 717 if changed.whois then |
| 742 msg.tags[1]:tag('status', {code = '104'}):up(); | 718 local code = (self:get_whois() == 'moderators') and "173" or "172"; |
| 743 end | |
| 744 if whois_changed then | |
| 745 local code = (whois == 'moderators') and "173" or "172"; | |
| 746 msg.tags[1]:tag('status', {code = code}):up(); | 719 msg.tags[1]:tag('status', {code = code}):up(); |
| 747 end | 720 end |
| 748 | |
| 749 self:broadcast_message(msg, false) | 721 self:broadcast_message(msg, false) |
| 750 end | 722 end |
| 751 end | 723 end |
| 752 | 724 |
| 753 function room_mt:destroy(newjid, reason, password) | 725 function room_mt:destroy(newjid, reason, password) |
| 879 end | 851 end |
| 880 elseif type == "set" or type == "get" then | 852 elseif type == "set" or type == "get" then |
| 881 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); | 853 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
| 882 end | 854 end |
| 883 elseif stanza.name == "message" and type == "groupchat" then | 855 elseif stanza.name == "message" and type == "groupchat" then |
| 884 local from, to = stanza.attr.from, stanza.attr.to; | 856 local from = stanza.attr.from; |
| 885 local current_nick = self._jid_nick[from]; | 857 local current_nick = self._jid_nick[from]; |
| 886 local occupant = self._occupants[current_nick]; | 858 local occupant = self._occupants[current_nick]; |
| 887 if not occupant then -- not in room | 859 if not occupant then -- not in room |
| 888 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); | 860 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); |
| 889 elseif occupant.role == "visitor" then | 861 elseif occupant.role == "visitor" then |
| 890 origin.send(st.error_reply(stanza, "auth", "forbidden")); | 862 origin.send(st.error_reply(stanza, "auth", "forbidden")); |
| 891 else | 863 else |
| 892 local from = stanza.attr.from; | 864 local from = stanza.attr.from; |
| 893 stanza.attr.from = current_nick; | 865 stanza.attr.from = current_nick; |
| 894 local subject = getText(stanza, {"subject"}); | 866 local subject = stanza:get_child_text("subject"); |
| 895 if subject then | 867 if subject then |
| 896 if occupant.role == "moderator" or | 868 if occupant.role == "moderator" or |
| 897 ( self._data.changesubject and occupant.role == "participant" ) then -- and participant | 869 ( self._data.changesubject and occupant.role == "participant" ) then -- and participant |
| 898 self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza | 870 self:set_subject(current_nick, subject); |
| 899 else | 871 else |
| 900 stanza.attr.from = from; | 872 stanza.attr.from = from; |
| 901 origin.send(st.error_reply(stanza, "auth", "forbidden")); | 873 origin.send(st.error_reply(stanza, "auth", "forbidden")); |
| 902 end | 874 end |
| 903 else | 875 else |
| 941 :text(_reason or "") | 913 :text(_reason or "") |
| 942 :up() | 914 :up() |
| 943 :tag('body') -- Add a plain message for clients which don't support invites | 915 :tag('body') -- Add a plain message for clients which don't support invites |
| 944 :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) | 916 :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) |
| 945 :up(); | 917 :up(); |
| 946 if self:is_members_only() and not self:get_affiliation(_invitee) then | 918 if self:get_members_only() and not self:get_affiliation(_invitee) then |
| 947 log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); | 919 log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); |
| 948 self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) | 920 self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) |
| 949 end | 921 end |
| 950 self:_route_stanza(invite); | 922 self:_route_stanza(invite); |
| 951 else | 923 else |
