Software /
code /
prosody
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 |
parent | 6049:6d410ffd6e13 |
parent | 6004:09151d26560a |
child | 6144:cb08bba0443a |
child | 6831: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 |