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