Comparison

plugins/muc/muc.lib.lua @ 6179:e488a90195bc

plugins/muc: Massive refactor We now have occupant objects; you grab them, modify them, save them. New presence handling code. Modify all presence sending to go via new functions.
author daurnimator <quae@daurnimator.com>
date Thu, 27 Mar 2014 19:16:13 -0400
parent 6143:82b3a2155a55
child 6180:35388114439f
comparison
equal deleted inserted replaced
6143:82b3a2155a55 6179:e488a90195bc
23 local t_insert, t_remove = table.insert, table.remove; 23 local t_insert, t_remove = table.insert, table.remove;
24 local setmetatable = setmetatable; 24 local setmetatable = setmetatable;
25 local base64 = require "util.encodings".base64; 25 local base64 = require "util.encodings".base64;
26 local md5 = require "util.hashes".md5; 26 local md5 = require "util.hashes".md5;
27 27
28 local occupant_lib = module:require "muc/occupant"
29
28 local default_history_length, max_history_length = 20, math.huge; 30 local default_history_length, max_history_length = 20, math.huge;
29
30 local get_filtered_presence do
31 local presence_filters = {
32 ["http://jabber.org/protocol/muc"] = true;
33 ["http://jabber.org/protocol/muc#user"] = true;
34 }
35 local function presence_filter(tag)
36 if presence_filters[tag.attr.xmlns] then
37 return nil;
38 end
39 return tag;
40 end
41 function get_filtered_presence(stanza)
42 return st.clone(stanza):maptags(presence_filter);
43 end
44 end
45 31
46 local is_kickable_error do 32 local is_kickable_error do
47 local kickable_error_conditions = { 33 local kickable_error_conditions = {
48 ["gone"] = true; 34 ["gone"] = true;
49 ["internal-server-error"] = true; 35 ["internal-server-error"] = true;
94 end 80 end
95 function room_mt:is_locked() 81 function room_mt:is_locked()
96 return not not self.locked 82 return not not self.locked
97 end 83 end
98 84
99 function room_mt:route_to_occupant(o_data, stanza) 85 --- Occupant functions
86 function room_mt:new_occupant(bare_real_jid, nick)
87 local occupant = occupant_lib.new(bare_real_jid, nick);
88 local affiliation = self:get_affiliation(bare_real_jid);
89 occupant.role = self:get_default_role(affiliation);
90 return occupant;
91 end
92
93 function room_mt:get_occupant_by_nick(nick)
94 local occupant = self._occupants[nick];
95 if occupant == nil then return nil end
96 return occupant_lib.copy(occupant);
97 end
98
99 do
100 local function next_copied_occupant(occupants, occupant_jid)
101 local next_occupant_jid, raw_occupant = next(occupants, occupant_jid);
102 if next_occupant_jid == nil then return nil end
103 return next_occupant_jid, occupant_lib.copy(raw_occupant);
104 end
105 function room_mt:each_occupant(read_only)
106 return next_copied_occupant, self._occupants, nil;
107 end
108 end
109
110 function room_mt:get_occupant_by_real_jid(real_jid)
111 local occupant_jid = self:get_occupant_jid(real_jid);
112 if occupant_jid == nil then return nil end
113 return self:get_occupant_by_nick(occupant_jid);
114 end
115
116 function room_mt:save_occupant(occupant)
117 occupant = occupant_lib.copy(occupant); -- So that occupant can be modified more
118 local id = occupant.nick
119
120 -- Need to maintain _jid_nick secondary index
121 local old_occupant = self._occupants[id];
122 if old_occupant then
123 for real_jid in pairs(old_occupant.sessions) do
124 self._jid_nick[real_jid] = nil;
125 end
126 end
127 if occupant.role ~= nil and next(occupant.sessions) then
128 for real_jid, presence in occupant:each_session() do
129 self._jid_nick[real_jid] = occupant.nick;
130 end
131 else
132 occupant = nil
133 end
134 self._occupants[id] = occupant
135 end
136
137 function room_mt:route_to_occupant(occupant, stanza)
100 local to = stanza.attr.to; 138 local to = stanza.attr.to;
101 for jid in pairs(o_data.sessions) do 139 for jid, pr in occupant:each_session() do
102 stanza.attr.to = jid; 140 if pr.attr.type ~= "unavailable" then
103 self:_route_stanza(stanza); 141 stanza.attr.to = jid;
142 self:route_stanza(stanza);
143 end
104 end 144 end
105 stanza.attr.to = to; 145 stanza.attr.to = to;
106 end 146 end
107 147
108 function room_mt:broadcast_presence(stanza, sid, code, nick) 148 -- Adds an item to an "x" element.
109 stanza = get_filtered_presence(stanza); 149 -- actor is the attribute table
110 local occupant = self._occupants[stanza.attr.from]; 150 local function add_item(x, affiliation, role, jid, nick, actor, reason)
111 stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) 151 x:tag("item", {affiliation = affiliation; role = role; jid = jid; nick = nick;})
112 :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none", nick=nick}):up(); 152 if actor then
113 if code then 153 x:tag("actor", actor):up()
114 stanza:tag("status", {code=code}):up(); 154 end
115 end 155 if reason then
116 self:broadcast_except_nick(stanza, stanza.attr.from); 156 x:tag("reason"):text(reason):up()
117 stanza:tag("status", {code='110'}):up(); 157 end
118 stanza.attr.to = sid; 158 x:up();
119 self:_route_stanza(stanza); 159 return x
120 end 160 end
161 -- actor is (real) jid
162 function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason)
163 local affiliation = self:get_affiliation(occupant.bare_jid);
164 local role = occupant.role;
165 local actor_jid = actor and self:get_occupant_jid(actor);
166 if actor then
167 actor = {nick = select(3,jid_split(actor_jid))};
168 end
169 if is_anonymous then
170 add_item(x, affiliation, role, nil, nick, actor, reason);
171 else
172 if actor_jid then
173 actor.jid = actor_jid;
174 end
175 for real_jid, session in occupant:each_session() do
176 add_item(x, affiliation, role, real_jid, nick, actor, reason);
177 end
178 end
179 return x
180 end
181
121 function room_mt:broadcast_message(stanza, historic) 182 function room_mt:broadcast_message(stanza, historic)
122 module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); 183 module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic});
123 self:broadcast(stanza); 184 self:broadcast(stanza);
124 end 185 end
125 186
137 stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) 198 stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
138 local entry = { stanza = stanza, timestamp = ts }; 199 local entry = { stanza = stanza, timestamp = ts };
139 t_insert(history, entry); 200 t_insert(history, entry);
140 while #history > room:get_historylength() do t_remove(history, 1) end 201 while #history > room:get_historylength() do t_remove(history, 1) end
141 end 202 end
142 end) 203 end);
143
144 function room_mt:broadcast_except_nick(stanza, nick)
145 return self:broadcast(stanza, function(rnick, occupant) return rnick ~= nick end)
146 end
147 204
148 -- Broadcast a stanza to all occupants in the room. 205 -- Broadcast a stanza to all occupants in the room.
149 -- optionally checks conditional called with nicl 206 -- optionally checks conditional called with (nick, occupant)
150 function room_mt:broadcast(stanza, cond_func) 207 function room_mt:broadcast(stanza, cond_func)
151 for nick, occupant in pairs(self._occupants) do 208 for nick, occupant in self:each_occupant() do
152 if cond_func == nil or cond_func(nick, occupant) then 209 if cond_func == nil or cond_func(nick, occupant) then
153 self:route_to_occupant(occupant, stanza) 210 self:route_to_occupant(occupant, stanza)
154 end 211 end
155 end 212 end
156 end 213 end
157 214
158 function room_mt:send_occupant_list(to) 215 -- Broadcasts an occupant's presence to the whole room
159 local current_nick = self:get_occupant_jid(to); 216 -- Takes (and modifies) the x element that goes into the stanzas
160 for occupant, o_data in pairs(self._occupants) do 217 function room_mt:publicise_occupant_status(occupant, full_x, actor, reason)
161 if occupant ~= current_nick then 218 local anon_x;
162 local pres = get_filtered_presence(o_data.sessions[o_data.jid]); 219 local has_anonymous = self:get_whois() ~= "anyone";
163 pres.attr.to, pres.attr.from = to, occupant; 220 if has_anonymous then
164 pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) 221 anon_x = st.clone(full_x);
165 :tag("item", {affiliation=o_data.affiliation or "none", role=o_data.role or "none"}):up(); 222 self:build_item_list(occupant, anon_x, true, nil, actor, reason);
166 self:_route_stanza(pres); 223 end
224 self:build_item_list(occupant,full_x, false, nil, actor, reason);
225
226 -- General populance
227 local full_p
228 if occupant.role ~= nil then
229 -- Try to use main jid's presence
230 local pr = occupant:get_presence();
231 if pr ~= nil then
232 full_p = st.clone(pr);
233 end
234 end
235 if full_p == nil then
236 full_p = st.presence{from=occupant.nick; type="unavailable"};
237 end
238 local anon_p;
239 if has_anonymous then
240 anon_p = st.clone(full_p);
241 anon_p:add_child(anon_x);
242 end
243 full_p:add_child(full_x);
244
245 for nick, n_occupant in self:each_occupant() do
246 if nick ~= occupant.nick or n_occupant.role == nil then
247 local pr = full_p;
248 if has_anonymous and n_occupant.role ~= "moderators" and occupant.bare_jid ~= n_occupant.bare_jid then
249 pr = anon_p;
250 end
251 self:route_to_occupant(n_occupant, pr);
252 end
253 end
254
255 -- Presences for occupant itself
256 full_x:tag("status", {code = "110";}):up();
257 if occupant.role == nil then
258 -- They get an unavailable
259 self:route_to_occupant(occupant, full_p);
260 else
261 -- use their own presences as templates
262 for full_jid, pr in occupant:each_session() do
263 if pr.attr.type ~= "unavailable" then
264 pr = st.clone(pr);
265 pr.attr.to = full_jid;
266 -- You can always see your own full jids
267 pr:add_child(full_x);
268 self:route_stanza(pr);
269 end
270 end
271 end
272 end
273
274 function room_mt:send_occupant_list(to, filter)
275 local to_occupant = self:get_occupant_by_real_jid(to);
276 local has_anonymous = self:get_whois() ~= "anyone"
277 for occupant_jid, occupant in self:each_occupant() do
278 if filter and filter(occupant_jid, occupant) then
279 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'});
280 local is_anonymous = has_anonymous and occupant.role ~= "moderator" and to_occupant.bare_jid ~= occupant.bare_jid;
281 self:build_item_list(occupant, x, is_anonymous);
282 local pres = st.clone(occupant:get_presence());
283 pres.attr.to = to;
284 pres:add_child(x);
285 self:route_stanza(pres);
167 end 286 end
168 end 287 end
169 end 288 end
170 289
171 local function parse_history(stanza) 290 local function parse_history(stanza)
246 maxchars = maxchars, maxstanzas = maxstanzas, since = since; 365 maxchars = maxchars, maxstanzas = maxstanzas, since = since;
247 next_stanza = function() end; -- events should define this iterator 366 next_stanza = function() end; -- events should define this iterator
248 } 367 }
249 module:fire_event("muc-get-history", event) 368 module:fire_event("muc-get-history", event)
250 for msg in event.next_stanza , event do 369 for msg in event.next_stanza , event do
251 self:_route_stanza(msg); 370 self:route_stanza(msg);
252 end 371 end
253 end 372 end
254 373
255 function room_mt:get_disco_info(stanza) 374 function room_mt:get_disco_info(stanza)
256 local count = 0; for _ in pairs(self._occupants) do count = count + 1; end 375 local count = 0; for _ in self:each_occupant() do count = count + 1; end
257 return st.reply(stanza):query("http://jabber.org/protocol/disco#info") 376 return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
258 :tag("identity", {category="conference", type="text", name=self:get_name()}):up() 377 :tag("identity", {category="conference", type="text", name=self:get_name()}):up()
259 :tag("feature", {var="http://jabber.org/protocol/muc"}):up() 378 :tag("feature", {var="http://jabber.org/protocol/muc"}):up()
260 :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up() 379 :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
261 :tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up() 380 :tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
270 }):form({["muc#roominfo_description"] = self:get_description()}, 'result')) 389 }):form({["muc#roominfo_description"] = self:get_description()}, 'result'))
271 ; 390 ;
272 end 391 end
273 function room_mt:get_disco_items(stanza) 392 function room_mt:get_disco_items(stanza)
274 local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); 393 local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
275 for room_jid in pairs(self._occupants) do 394 for room_jid in self:each_occupant() do
276 reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up(); 395 reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up();
277 end 396 end
278 return reply; 397 return reply;
279 end 398 end
280 399
289 local from, subject = self:get_subject() 408 local from, subject = self:get_subject()
290 if subject then 409 if subject then
291 local msg = create_subject_message(subject) 410 local msg = create_subject_message(subject)
292 msg.attr.from = from 411 msg.attr.from = from
293 msg.attr.to = to 412 msg.attr.to = to
294 self:_route_stanza(msg); 413 self:route_stanza(msg);
295 end 414 end
296 end 415 end
297 function room_mt:set_subject(current_nick, subject) 416 function room_mt:set_subject(current_nick, subject)
298 if subject == "" then subject = nil; end 417 if subject == "" then subject = nil; end
299 self._data['subject'] = subject; 418 self._data['subject'] = subject;
304 self:broadcast_message(msg, false); 423 self:broadcast_message(msg, false);
305 return true; 424 return true;
306 end 425 end
307 426
308 function room_mt:handle_kickable(origin, stanza) 427 function room_mt:handle_kickable(origin, stanza)
428 local real_jid = stanza.attr.from;
429 local occupant = self:get_occupant_by_real_jid(real_jid);
430 if occupant == nil then return nil; end
309 local type, condition, text = stanza:get_error(); 431 local type, condition, text = stanza:get_error();
310 local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); 432 local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error");
311 if text then 433 if text then
312 error_message = error_message..": "..text; 434 error_message = error_message..": "..text;
313 end 435 end
314 local kick_stanza = st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) 436 occupant:set_session(real_jid, st.presence({type="unavailable"})
315 :tag('status'):text(error_message); 437 :tag('status'):text(error_message));
316 self:handle_unavailable_to_occupant(origin, kick_stanza); -- send unavailable 438 self:save_occupant(occupant);
439 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";})
440 :tag("status", {code = "307"})
441 self:publicise_occupant_status(occupant, x);
317 return true; 442 return true;
318 end 443 end
319 444
320 function room_mt:set_name(name) 445 function room_mt:set_name(name)
321 if name == "" or type(name) ~= "string" or name == (jid_split(self.jid)) then name = nil; end 446 if name == "" or type(name) ~= "string" or name == (jid_split(self.jid)) then name = nil; end
426 551
427 function room_mt:get_whois() 552 function room_mt:get_whois()
428 return self._data.whois; 553 return self._data.whois;
429 end 554 end
430 555
431 function room_mt:handle_unavailable_to_occupant(origin, stanza) 556 module:hook("muc-room-pre-create", function(event)
432 local from = stanza.attr.from; 557 local room = event.room;
433 local current_nick = self:get_occupant_jid(from); 558 if room:is_locked() and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then
434 if not current_nick then 559 room:unlock(); -- Older groupchat protocol doesn't lock
435 return true; -- discard 560 end
436 end 561 end, 10);
437 local pr = get_filtered_presence(stanza); 562
438 pr.attr.from = current_nick; 563 -- Give the room creator owner affiliation
439 log("debug", "%s leaving %s", current_nick, self.jid); 564 module:hook("muc-room-pre-create", function(event)
440 self._jid_nick[from] = nil; 565 event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner");
441 local occupant = self._occupants[current_nick]; 566 end, -1);
442 local new_jid = next(occupant.sessions);
443 if new_jid == from then new_jid = next(occupant.sessions, new_jid); end
444 if new_jid then
445 local jid = occupant.jid;
446 occupant.jid = new_jid;
447 occupant.sessions[from] = nil;
448 pr.attr.to = from;
449 pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
450 :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up()
451 :tag("status", {code='110'}):up();
452 self:_route_stanza(pr);
453 if jid ~= new_jid then
454 pr = st.clone(occupant.sessions[new_jid])
455 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
456 :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"});
457 pr.attr.from = current_nick;
458 self:broadcast_except_nick(pr, current_nick);
459 end
460 else
461 occupant.role = 'none';
462 self:broadcast_presence(pr, from);
463 self._occupants[current_nick] = nil;
464 module:fire_event("muc-occupant-left", { room = self; nick = current_nick; });
465 end
466 return true;
467 end
468
469 function room_mt:handle_occupant_presence(origin, stanza)
470 local from = stanza.attr.from;
471 local pr = get_filtered_presence(stanza);
472 local current_nick = stanza.attr.to
473 pr.attr.from = current_nick;
474 log("debug", "%s broadcasted presence", current_nick);
475 self._occupants[current_nick].sessions[from] = pr;
476 self:broadcast_presence(pr, from);
477 return true;
478 end
479
480 function room_mt:handle_change_nick(origin, stanza, current_nick, to)
481 local from = stanza.attr.from;
482 local occupant = self._occupants[current_nick];
483 local is_multisession = next(occupant.sessions, next(occupant.sessions));
484 if self._occupants[to] or is_multisession then
485 log("debug", "%s couldn't change nick", current_nick);
486 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
487 reply.tags[1].attr.code = "409";
488 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
489 return true;
490 else
491 local to_nick = select(3, jid_split(to));
492 log("debug", "%s (%s) changing nick to %s", current_nick, occupant.jid, to);
493 local p = st.presence({type='unavailable', from=current_nick});
494 self:broadcast_presence(p, from, '303', to_nick);
495 self._occupants[current_nick] = nil;
496 self._occupants[to] = occupant;
497 self._jid_nick[from] = to;
498 local pr = get_filtered_presence(stanza);
499 pr.attr.from = to;
500 self._occupants[to].sessions[from] = pr;
501 self:broadcast_presence(pr, from);
502 return true;
503 end
504 end
505 567
506 module:hook("muc-occupant-pre-join", function(event) 568 module:hook("muc-occupant-pre-join", function(event)
507 return module:fire_event("muc-occupant-pre-join/affiliation", event) 569 return module:fire_event("muc-occupant-pre-join/affiliation", event)
508 or module:fire_event("muc-occupant-pre-join/password", event) 570 or module:fire_event("muc-occupant-pre-join/password", event)
509 or module:fire_event("muc-occupant-pre-join/locked", event) 571 or module:fire_event("muc-occupant-pre-join/locked", event);
510 or module:fire_event("muc-occupant-pre-join/nick-conflict", event)
511 end, -1) 572 end, -1)
512 573
513 module:hook("muc-occupant-pre-join/password", function(event) 574 module:hook("muc-occupant-pre-join/password", function(event)
514 local room, stanza = event.room, event.stanza; 575 local room, stanza = event.room, event.stanza;
515 local from, to = stanza.attr.from, stanza.attr.to;
516 local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); 576 local password = stanza:get_child("x", "http://jabber.org/protocol/muc");
517 password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); 577 password = password and password:get_child_text("password", "http://jabber.org/protocol/muc");
518 if not password or password == "" then password = nil; end 578 if not password or password == "" then password = nil; end
519 if room:get_password() ~= password then 579 if room:get_password() ~= password then
520 local from, to = stanza.attr.from, stanza.attr.to; 580 local from, to = stanza.attr.from, stanza.attr.to;
524 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); 584 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
525 return true; 585 return true;
526 end 586 end
527 end, -1) 587 end, -1)
528 588
529 module:hook("muc-occupant-pre-join/nick-conflict", function(event)
530 local room, stanza = event.room, event.stanza;
531 local from, to = stanza.attr.from, stanza.attr.to;
532 local occupant = room._occupants[to]
533 if occupant -- occupant already exists
534 and jid_bare(from) ~= jid_bare(occupant.jid) then -- and has different bare real jid
535 log("debug", "%s couldn't join due to nick conflict: %s", from, to);
536 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
537 reply.tags[1].attr.code = "409";
538 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
539 return true;
540 end
541 end, -1)
542
543 module:hook("muc-occupant-pre-join/locked", function(event) 589 module:hook("muc-occupant-pre-join/locked", function(event)
544 if event.room:is_locked() then -- Deny entry 590 if event.room:is_locked() then -- Deny entry
545 event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); 591 event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found"));
546 return true; 592 return true;
547 end 593 end
548 end, -1) 594 end, -1);
549
550 function room_mt:handle_join(origin, stanza)
551 local from, to = stanza.attr.from, stanza.attr.to;
552 local affiliation = self:get_affiliation(from);
553 if affiliation == nil and next(self._affiliations) == nil then -- new room, no owners
554 affiliation = "owner";
555 self._affiliations[jid_bare(from)] = affiliation;
556 if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then
557 self:unlock(); -- Older groupchat protocol doesn't lock
558 end
559 end
560 if module:fire_event("muc-occupant-pre-join", {
561 room = self;
562 origin = origin;
563 stanza = stanza;
564 affiliation = affiliation;
565 }) then return true; end
566 log("debug", "%s joining as %s", from, to);
567
568 local role = self:get_default_role(affiliation)
569 if role then -- new occupant
570 local is_merge = not not self._occupants[to]
571 if not is_merge then
572 self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}};
573 else
574 self._occupants[to].sessions[from] = get_filtered_presence(stanza);
575 end
576 self._jid_nick[from] = to;
577 self:send_occupant_list(from);
578 local pr = get_filtered_presence(stanza);
579 pr.attr.from = to;
580 pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
581 :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up();
582 if not is_merge then
583 self:broadcast_except_nick(pr, to);
584 end
585 pr:tag("status", {code='110'}):up();
586 if self:get_whois() == 'anyone' then
587 pr:tag("status", {code='100'}):up();
588 end
589 if self:is_locked() then
590 pr:tag("status", {code='201'}):up();
591 end
592 pr.attr.to = from;
593 self:_route_stanza(pr);
594 self:send_history(from, stanza);
595 self:send_subject(from);
596 return true;
597 end
598 end
599 595
600 -- registration required for entering members-only room 596 -- registration required for entering members-only room
601 module:hook("muc-occupant-pre-join/affiliation", function(event) 597 module:hook("muc-occupant-pre-join/affiliation", function(event)
602 if event.affiliation == nil and event.room:get_members_only() then 598 local room, stanza = event.room, event.stanza;
603 local reply = st.error_reply(event.stanza, "auth", "registration-required"):up(); 599 local affiliation = room:get_affiliation(stanza.attr.from);
600 if affiliation == nil and event.room:get_members_only() then
601 local reply = st.error_reply(stanza, "auth", "registration-required"):up();
604 reply.tags[1].attr.code = "407"; 602 reply.tags[1].attr.code = "407";
605 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); 603 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
606 return true; 604 return true;
607 end 605 end
608 end, -1) 606 end, -1)
609 607
610 -- banned 608 -- banned
611 module:hook("muc-occupant-pre-join/affiliation", function(event) 609 module:hook("muc-occupant-pre-join/affiliation", function(event)
612 if event.affiliation == "outcast" then 610 local room, stanza = event.room, event.stanza;
613 local reply = st.error_reply(event.stanza, "auth", "forbidden"):up(); 611 local affiliation = room:get_affiliation(stanza.attr.from);
612 if affiliation == "outcast" then
613 local reply = st.error_reply(stanza, "auth", "forbidden"):up();
614 reply.tags[1].attr.code = "403"; 614 reply.tags[1].attr.code = "403";
615 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); 615 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
616 return true; 616 return true;
617 end 617 end
618 end, -1) 618 end, -1)
619 619
620 function room_mt:handle_available_to_occupant(origin, stanza) 620 module:hook("muc-occupant-joined", function(event)
621 local from, to = stanza.attr.from, stanza.attr.to; 621 local room, stanza = event.room, event.stanza;
622 local current_nick = self:get_occupant_jid(from); 622 local real_jid = stanza.attr.from;
623 if current_nick then 623 room:send_occupant_list(real_jid, function(nick, occupant)
624 --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence 624 -- Don't include self
625 if current_nick == to then -- simple presence 625 return occupant.sessions[real_jid] == nil
626 return self:handle_occupant_presence(origin, stanza) 626 end);
627 else -- change nick 627 room:send_history(stanza);
628 return self:handle_change_nick(origin, stanza, current_nick, to) 628 room:send_subject(real_jid);
629 end 629 end, -1);
630 --else -- possible rejoin
631 -- log("debug", "%s had connection replaced", current_nick);
632 -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
633 -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable
634 -- self:handle_to_occupant(origin, stanza); -- resend available
635 --end
636 else -- enter room
637 return self:handle_join(origin, stanza)
638 end
639 end
640 630
641 function room_mt:handle_presence_to_occupant(origin, stanza) 631 function room_mt:handle_presence_to_occupant(origin, stanza)
642 local type = stanza.attr.type; 632 local type = stanza.attr.type;
643 if type == "error" then -- error, kick em out! 633 if type == "error" then -- error, kick em out!
644 return self:handle_kickable(origin, stanza) 634 return self:handle_kickable(origin, stanza)
645 elseif type == "unavailable" then -- unavailable 635 elseif type == nil or type == "unavailable" then
646 return self:handle_unavailable_to_occupant(origin, stanza) 636 local real_jid = stanza.attr.from;
647 elseif not type then -- available 637 local bare_jid = jid_bare(real_jid);
648 return self:handle_available_to_occupant(origin, stanza) 638 local orig_occupant, dest_occupant;
639 local is_new_room = next(self._affiliations) == nil;
640 if is_new_room then
641 if type == "unavailable" then return true; end -- Unavailable from someone not in the room
642 if module:fire_event("muc-room-pre-create", {
643 room = self;
644 origin = origin;
645 stanza = stanza;
646 }) then return true; end
647 else
648 orig_occupant = self:get_occupant_by_real_jid(real_jid);
649 if type == "unavailable" and orig_occupant == nil then return true; end -- Unavailable from someone not in the room
650 end
651 local is_first_dest_session;
652 if type == "unavailable" then
653 -- dest_occupant = nil
654 elseif orig_occupant and orig_occupant.nick == stanza.attr.to then -- Just a presence update
655 log("debug", "presence update for %s from session %s", orig_occupant.nick, real_jid);
656 dest_occupant = orig_occupant;
657 else
658 local dest_jid = stanza.attr.to;
659 dest_occupant = self:get_occupant_by_nick(dest_jid);
660 if dest_occupant == nil then
661 log("debug", "no occupant found for %s; creating new occupant object for %s", dest_jid, real_jid);
662 is_first_dest_session = true;
663 dest_occupant = self:new_occupant(bare_jid, dest_jid);
664 else
665 is_first_dest_session = false;
666 end
667 end
668 local is_last_orig_session;
669 if orig_occupant ~= nil then
670 -- Is there are least 2 sessions?
671 is_last_orig_session = next(orig_occupant.sessions, next(orig_occupant.sessions)) == nil;
672 end
673
674 local event, event_name = {
675 room = self;
676 origin = origin;
677 stanza = stanza;
678 is_first_session = is_first_dest_session;
679 is_last_session = is_last_orig_session;
680 };
681 if orig_occupant == nil then
682 event_name = "muc-occupant-pre-join";
683 event.is_new_room = is_new_room;
684 elseif dest_occupant == nil then
685 event_name = "muc-occupant-pre-leave";
686 else
687 event_name = "muc-occupant-pre-change";
688 end
689 if module:fire_event(event_name, event) then return true; end
690
691 -- Check for nick conflicts
692 if dest_occupant ~= nil and not is_first_dest_session and bare_jid ~= jid_bare(dest_occupant.bare_jid) then -- new nick or has different bare real jid
693 log("debug", "%s couldn't join due to nick conflict: %s", real_jid, dest_occupant.nick);
694 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
695 reply.tags[1].attr.code = "409";
696 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
697 return true;
698 end
699
700 -- Send presence stanza about original occupant
701 if orig_occupant ~= nil and orig_occupant ~= dest_occupant then
702 local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";});
703
704 if dest_occupant == nil then -- Session is leaving
705 log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick);
706 orig_occupant:set_session(real_jid, stanza);
707 else
708 log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick);
709 orig_occupant:remove_session(real_jid); -- If we are moving to a new nick; we don't want to get our own presence
710
711 local dest_nick = select(3, jid_split(dest_occupant.nick));
712 local affiliation = self:get_affiliation(bare_jid);
713
714 -- This session
715 if not is_first_dest_session then -- User is swapping into another pre-existing session
716 log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid, dest_occupant.nick);
717 -- Show the other session leaving
718 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";})
719 :tag("status"):text("you are joining pre-existing session " .. dest_nick):up();
720 add_item(x, affiliation, "none");
721 local pr = st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"}
722 :add_child(x);
723 self:route_stanza(pr);
724 else
725 if is_last_orig_session then -- User is moving to a new session
726 log("debug", "no sessions in %s left; marking as nick change", orig_occupant.nick);
727 -- Everyone gets to see this as a nick change
728 local jid = self:get_whois() ~= "anyone" and real_jid or nil; -- FIXME: mods should see real jids
729 add_item(orig_x, affiliation, orig_occupant.role, jid, dest_nick);
730 orig_x:tag("status", {code = "303";}):up();
731 end
732 end
733 -- The session itself always sees a nick change
734 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";});
735 add_item(x, affiliation, orig_occupant.role, real_jid, dest_nick);
736 -- self:build_item_list(orig_occupant, x, false); -- COMPAT
737 x:tag("status", {code = "303";}):up();
738 x:tag("status", {code = "110";}):up();
739 self:route_stanza(st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x));
740 end
741 self:save_occupant(orig_occupant);
742 self:publicise_occupant_status(orig_occupant, orig_x);
743
744 if is_last_orig_session then
745 module:fire_event("muc-occupant-left", {room = self; nick = orig_occupant.nick;});
746 end
747 end
748
749 if dest_occupant ~= nil then
750 dest_occupant:set_session(real_jid, stanza);
751 local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";});
752 if is_new_room then
753 dest_x:tag("status", {code = "201"}):up();
754 end
755 if orig_occupant == nil and self:get_whois() == "anyone" then
756 dest_x:tag("status", {code = "100"}):up();
757 end
758 self:save_occupant(dest_occupant);
759 self:publicise_occupant_status(dest_occupant, dest_x);
760
761 if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then -- If user is swapping and wasn't last original session
762 log("debug", "session %s split nicks; showing %s rejoining", real_jid, orig_occupant.nick);
763 -- Show the original nick joining again
764 local pr = st.clone(orig_occupant:get_presence());
765 pr.attr.to = real_jid;
766 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";});
767 self:build_item_list(orig_occupant, x, false);
768 -- TODO: new status code to inform client this was the multi-session it left?
769 pr:add_child(x);
770 self:route_stanza(pr);
771 end
772
773 if orig_occupant == nil and is_first_dest_session then
774 module:fire_event("muc-occupant-joined", {room = self; nick = dest_occupant.nick; stanza = stanza;});
775 end
776 end
649 elseif type ~= 'result' then -- bad type 777 elseif type ~= 'result' then -- bad type
650 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences 778 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
651 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? 779 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
652 end 780 end
653 end 781 end
657 function room_mt:handle_iq_to_occupant(origin, stanza) 785 function room_mt:handle_iq_to_occupant(origin, stanza)
658 local from, to = stanza.attr.from, stanza.attr.to; 786 local from, to = stanza.attr.from, stanza.attr.to;
659 local type = stanza.attr.type; 787 local type = stanza.attr.type;
660 local id = stanza.attr.id; 788 local id = stanza.attr.id;
661 local current_nick = self:get_occupant_jid(from); 789 local current_nick = self:get_occupant_jid(from);
662 local o_data = self._occupants[to]; 790 local occupant = self:get_occupant_by_nick(to);
663 if (type == "error" or type == "result") then 791 if (type == "error" or type == "result") then
664 do -- deconstruct_stanza_id 792 do -- deconstruct_stanza_id
665 if not current_nick or not o_data then return nil; end 793 if not current_nick or not occupant then return nil; end
666 local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); 794 local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$");
667 if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end 795 if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end
668 local session_jid 796 local session_jid
669 for to_jid in pairs(o_data.sessions) do 797 for to_jid in occupant:each_session() do
670 if md5(to_jid) == to_jid_hash then 798 if md5(to_jid) == to_jid_hash then
671 session_jid = to_jid; 799 session_jid = to_jid;
672 break; 800 break;
673 end 801 end
674 end 802 end
675 if session_jid == nil then return nil; end 803 if session_jid == nil then return nil; end
676 stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id 804 stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id
677 end 805 end
678 log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); 806 log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to);
679 self:_route_stanza(stanza); 807 self:route_stanza(stanza);
680 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; 808 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
681 return true; 809 return true;
682 else -- Type is "get" or "set" 810 else -- Type is "get" or "set"
683 if not current_nick then 811 if not current_nick then
684 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); 812 origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
685 return true; 813 return true;
686 end 814 end
687 if not o_data then -- recipient not in room 815 if not occupant then -- recipient not in room
688 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); 816 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
689 return true; 817 return true;
690 end 818 end
691 do -- construct_stanza_id 819 do -- construct_stanza_id
692 stanza.attr.id = base64.encode(o_data.jid.."\0"..stanza.attr.id.."\0"..md5(from)); 820 stanza.attr.id = base64.encode(occupant.jid.."\0"..stanza.attr.id.."\0"..md5(from));
693 end 821 end
694 stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; 822 stanza.attr.from, stanza.attr.to = current_nick, occupant.jid;
695 log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); 823 log("debug", "%s sent private iq stanza to %s (%s)", from, to, occupant.jid);
696 if stanza.tags[1].attr.xmlns == 'vcard-temp' then 824 if stanza.tags[1].attr.xmlns == 'vcard-temp' then
697 stanza.attr.to = jid_bare(stanza.attr.to); 825 stanza.attr.to = jid_bare(stanza.attr.to);
698 end 826 end
699 self:_route_stanza(stanza); 827 self:route_stanza(stanza);
700 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; 828 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
701 return true; 829 return true;
702 end 830 end
703 end 831 end
704 832
718 elseif type == "error" and is_kickable_error(stanza) then 846 elseif type == "error" and is_kickable_error(stanza) then
719 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); 847 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
720 return self:handle_kickable(origin, stanza); -- send unavailable 848 return self:handle_kickable(origin, stanza); -- send unavailable
721 end 849 end
722 850
723 local o_data = self._occupants[to]; 851 local o_data = self:get_occupant_by_nick(to);
724 if not o_data then 852 if not o_data then
725 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); 853 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
726 return true; 854 return true;
727 end 855 end
728 log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); 856 log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid);
868 end 996 end
869 self:broadcast_message(msg, false) 997 self:broadcast_message(msg, false)
870 end 998 end
871 end 999 end
872 1000
1001 -- Removes everyone from the room
1002 function room_mt:clear(x)
1003 x = x or st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'});
1004 local occupants_updated = {};
1005 for nick, occupant in self:each_occupant() do
1006 occupant.role = nil;
1007 self:save_occupant(occupant);
1008 occupants_updated[occupant] = true;
1009 end
1010 for occupant in pairs(occupants_updated) do
1011 self:publicise_occupant_status(occupant, x);
1012 module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; });
1013 end
1014 end
1015
873 function room_mt:destroy(newjid, reason, password) 1016 function room_mt:destroy(newjid, reason, password)
874 local pr = st.presence({type = "unavailable"}) 1017 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
875 :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) 1018 :tag("item", { affiliation='none', role='none' }):up()
876 :tag("item", { affiliation='none', role='none' }):up() 1019 :tag("destroy", {jid=newjid});
877 :tag("destroy", {jid=newjid}) 1020 if reason then x:tag("reason"):text(reason):up(); end
878 if reason then pr:tag("reason"):text(reason):up(); end 1021 if password then x:tag("password"):text(password):up(); end
879 if password then pr:tag("password"):text(password):up(); end 1022 x:up();
880 for nick, occupant in pairs(self._occupants) do 1023 self:clear(x);
881 pr.attr.from = nick;
882 for jid in pairs(occupant.sessions) do
883 pr.attr.to = jid;
884 self:_route_stanza(pr);
885 self._jid_nick[jid] = nil;
886 end
887 self._occupants[nick] = nil;
888 module:fire_event("muc-occupant-left", { room = self; nick = nick; });
889 end
890 self:set_persistent(false); 1024 self:set_persistent(false);
891 module:fire_event("muc-room-destroyed", { room = self }); 1025 module:fire_event("muc-room-destroyed", { room = self });
892 end 1026 end
893 1027
894 function room_mt:handle_disco_info_get_query(origin, stanza) 1028 function room_mt:handle_disco_info_get_query(origin, stanza)
909 origin.send(st.error_reply(stanza, "modify", "jid-malformed")); 1043 origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
910 return true; 1044 return true;
911 end 1045 end
912 end 1046 end
913 if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation 1047 if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
914 local occupant = self._occupants[self.jid.."/"..item.attr.nick]; 1048 local occupant = self:get_occupant_by_nick(self.jid.."/"..item.attr.nick);
915 if occupant then item.attr.jid = occupant.jid; end 1049 if occupant then item.attr.jid = occupant.jid; end
916 elseif not item.attr.nick and item.attr.jid then 1050 elseif not item.attr.nick and item.attr.jid then
917 local nick = self:get_occupant_jid(item.attr.jid); 1051 local nick = self:get_occupant_jid(item.attr.jid);
918 if nick then item.attr.nick = select(3, jid_split(nick)); end 1052 if nick then item.attr.nick = select(3, jid_split(nick)); end
919 end 1053 end
956 end 1090 end
957 elseif _rol and not _aff then 1091 elseif _rol and not _aff then
958 local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); 1092 local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation);
959 if role == "moderator" then 1093 if role == "moderator" then
960 if _rol == "none" then _rol = nil; end 1094 if _rol == "none" then _rol = nil; end
961 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); 1095 self:send_occupant_list(actor, function(occupant_jid, occupant) return occupant.role == _rol end);
962 for occupant_jid, occupant in pairs(self._occupants) do
963 if occupant.role == _rol then
964 reply:tag("item", {
965 nick = select(3, jid_split(occupant_jid)),
966 role = _rol or "none",
967 affiliation = occupant.affiliation or "none",
968 jid = occupant.jid
969 }):up();
970 end
971 end
972 origin.send(reply);
973 return true; 1096 return true;
974 else 1097 else
975 origin.send(st.error_reply(stanza, "auth", "forbidden")); 1098 origin.send(st.error_reply(stanza, "auth", "forbidden"));
976 return true; 1099 return true;
977 end 1100 end
1013 end 1136 end
1014 end 1137 end
1015 1138
1016 function room_mt:handle_groupchat_to_room(origin, stanza) 1139 function room_mt:handle_groupchat_to_room(origin, stanza)
1017 local from = stanza.attr.from; 1140 local from = stanza.attr.from;
1018 local current_nick = self:get_occupant_jid(from); 1141 local occupant = self:get_occupant_by_real_jid(from);
1019 local occupant = self._occupants[current_nick];
1020 if not occupant then -- not in room 1142 if not occupant then -- not in room
1021 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); 1143 origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
1022 return true; 1144 return true;
1023 elseif occupant.role == "visitor" then 1145 elseif occupant.role == "visitor" then
1024 origin.send(st.error_reply(stanza, "auth", "forbidden")); 1146 origin.send(st.error_reply(stanza, "auth", "forbidden"));
1025 return true; 1147 return true;
1026 else 1148 else
1027 local from = stanza.attr.from; 1149 local from = stanza.attr.from;
1028 stanza.attr.from = current_nick; 1150 stanza.attr.from = occupant.nick;
1029 local subject = stanza:get_child_text("subject"); 1151 local subject = stanza:get_child_text("subject");
1030 if subject then 1152 if subject then
1031 if occupant.role == "moderator" or 1153 if occupant.role == "moderator" or
1032 ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant 1154 ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant
1033 self:set_subject(current_nick, subject); 1155 self:set_subject(occupant.nick, subject);
1034 else 1156 else
1035 stanza.attr.from = from; 1157 stanza.attr.from = from;
1036 origin.send(st.error_reply(stanza, "auth", "forbidden")); 1158 origin.send(st.error_reply(stanza, "auth", "forbidden"));
1037 end 1159 end
1038 else 1160 else
1094 return true; 1216 return true;
1095 end 1217 end
1096 end 1218 end
1097 1219
1098 module:hook("muc-invite", function(event) 1220 module:hook("muc-invite", function(event)
1099 event.room:_route_stanza(event.stanza); 1221 event.room:route_stanza(event.stanza);
1100 return true; 1222 return true;
1101 end, -1) 1223 end, -1)
1102 1224
1103 -- When an invite is sent; add an affiliation for the invitee 1225 -- When an invite is sent; add an affiliation for the invitee
1104 module:hook("muc-invite", function(event) 1226 module:hook("muc-invite", function(event)
1208 end 1330 end
1209 end 1331 end
1210 end 1332 end
1211 self._affiliations[jid] = affiliation; 1333 self._affiliations[jid] = affiliation;
1212 local role = self:get_default_role(affiliation); 1334 local role = self:get_default_role(affiliation);
1213 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) 1335 local occupants_updated = {};
1214 :tag("item", {affiliation=affiliation or "none", role=role or "none"}) 1336 for nick, occupant in self:each_occupant() do
1215 :tag("reason"):text(reason or ""):up() 1337 if occupant.bare_jid == jid then
1216 :up(); 1338 occupant.role = role;
1217 local presence_type = nil; 1339 self:save_occupant(occupant);
1340 occupants_updated[occupant] = true;
1341 end
1342 end
1343 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
1218 if not role then -- getting kicked 1344 if not role then -- getting kicked
1219 presence_type = "unavailable";
1220 if affiliation == "outcast" then 1345 if affiliation == "outcast" then
1221 x:tag("status", {code="301"}):up(); -- banned 1346 x:tag("status", {code="301"}):up(); -- banned
1222 else 1347 else
1223 x:tag("status", {code="321"}):up(); -- affiliation change 1348 x:tag("status", {code="321"}):up(); -- affiliation change
1224 end 1349 end
1225 end 1350 end
1226 local modified_nicks = {}; 1351 for occupant in pairs(occupants_updated) do
1227 for nick, occupant in pairs(self._occupants) do 1352 self:publicise_occupant_status(occupant, x, actor, reason);
1228 if jid_bare(occupant.jid) == jid then
1229 if not role then -- getting kicked
1230 self._occupants[nick] = nil;
1231 else
1232 occupant.affiliation, occupant.role = affiliation, role;
1233 end
1234 for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick
1235 if not role then self._jid_nick[jid] = nil; end
1236 local p = st.clone(pres);
1237 p.attr.from = nick;
1238 p.attr.type = presence_type;
1239 p.attr.to = jid;
1240 p:add_child(x);
1241 self:_route_stanza(p);
1242 if occupant.jid == jid then
1243 modified_nicks[nick] = p;
1244 end
1245 end
1246 end
1247 end 1353 end
1248 if self.save then self:save(); end 1354 if self.save then self:save(); end
1249 if callback then callback(); end 1355 if callback then callback(); end
1250 for nick,p in pairs(modified_nicks) do
1251 p.attr.from = nick;
1252 self:broadcast_except_nick(p, nick);
1253 end
1254 return true; 1356 return true;
1255 end 1357 end
1256 1358
1257 function room_mt:get_role(nick) 1359 function room_mt:get_role(nick)
1258 local session = self._occupants[nick]; 1360 local occupant = self:get_occupant_by_nick(nick);
1259 return session and session.role or nil; 1361 return occupant and occupant.role or nil;
1260 end 1362 end
1261 function room_mt:can_set_role(actor_jid, occupant_jid, role) 1363 function room_mt:can_set_role(actor_jid, occupant_jid, role)
1262 local occupant = self._occupants[occupant_jid]; 1364 local occupant = self:get_occupant_by_nick(occupant_jid);
1263 if not occupant or not actor_jid then return nil, "modify", "not-acceptable"; end 1365 if not occupant or not actor_jid then return nil, "modify", "not-acceptable"; end
1264 1366
1265 if actor_jid == true then return true; end 1367 if actor_jid == true then return true; end
1266 1368
1267 local actor = self._occupants[self:get_occupant_jid(actor_jid)]; 1369 local actor = self:get_occupant_by_real_jid(actor_jid);
1268 if actor.role == "moderator" then 1370 if actor.role == "moderator" then
1269 if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then 1371 local occupant_affiliation = self:get_affiliation(occupant.bare_jid)
1270 if actor.affiliation == "owner" or actor.affiliation == "admin" then 1372 local actor_affiliation = self:get_affiliation(actor.bare_jid)
1373 if occupant_affiliation ~= "owner" and occupant_affiliation ~= "admin" then
1374 if actor_affiliation == "owner" or actor_affiliation == "admin" then
1271 return true; 1375 return true;
1272 elseif occupant.role ~= "moderator" and role ~= "moderator" then 1376 elseif occupant.role ~= "moderator" and role ~= "moderator" then
1273 return true; 1377 return true;
1274 end 1378 end
1275 end 1379 end
1279 function room_mt:set_role(actor, occupant_jid, role, callback, reason) 1383 function room_mt:set_role(actor, occupant_jid, role, callback, reason)
1280 if role == "none" then role = nil; end 1384 if role == "none" then role = nil; end
1281 if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end 1385 if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
1282 local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); 1386 local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role);
1283 if not allowed then return allowed, err_type, err_condition; end 1387 if not allowed then return allowed, err_type, err_condition; end
1284 local occupant = self._occupants[occupant_jid]; 1388
1285 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) 1389 local occupant = self:get_occupant_by_nick(occupant_jid);
1286 :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) 1390 local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
1287 :tag("reason"):text(reason or ""):up() 1391 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
1288 :up(); 1392 if not role then
1289 local presence_type = nil;
1290 if not role then -- kick
1291 presence_type = "unavailable";
1292 self._occupants[occupant_jid] = nil;
1293 for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
1294 self._jid_nick[jid] = nil;
1295 end
1296 x:tag("status", {code = "307"}):up(); 1393 x:tag("status", {code = "307"}):up();
1297 else 1394 end
1298 occupant.role = role; 1395 occupant.role = role;
1299 end 1396 self:save_occupant(occupant);
1300 local bp; 1397 self:publicise_occupant_status(occupant, x, actor, reason);
1301 for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick
1302 local p = st.clone(pres);
1303 p.attr.from = occupant_jid;
1304 p.attr.type = presence_type;
1305 p.attr.to = jid;
1306 p:add_child(x);
1307 self:_route_stanza(p);
1308 if occupant.jid == jid then
1309 bp = p;
1310 end
1311 end
1312 if callback then callback(); end 1398 if callback then callback(); end
1313 if bp then
1314 self:broadcast_except_nick(bp, occupant_jid);
1315 end
1316 return true; 1399 return true;
1317 end
1318
1319 function room_mt:_route_stanza(stanza)
1320 local muc_child;
1321 if stanza.name == "presence" then
1322 local to_occupant = self._occupants[self:get_occupant_jid(stanza.attr.to)];
1323 local from_occupant = self._occupants[stanza.attr.from];
1324 if to_occupant and from_occupant then
1325 if self:get_whois() == 'anyone' then
1326 muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
1327 else
1328 if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then
1329 muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
1330 end
1331 end
1332 end
1333 if muc_child then
1334 for item in muc_child:childtags("item") do
1335 if from_occupant == to_occupant then
1336 item.attr.jid = stanza.attr.to;
1337 else
1338 item.attr.jid = from_occupant.jid;
1339 end
1340 end
1341 end
1342 end
1343 self:route_stanza(stanza);
1344 if muc_child then
1345 for item in muc_child:childtags("item") do
1346 item.attr.jid = nil;
1347 end
1348 end
1349 end 1400 end
1350 1401
1351 local _M = {}; -- module "muc" 1402 local _M = {}; -- module "muc"
1352 1403
1353 function _M.new_room(jid, config) 1404 function _M.new_room(jid, config)