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 |
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) } |
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(.+)$"); |
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 |