Software /
code /
prosody
Comparison
plugins/muc/muc.lib.lua @ 1735:81406277279e
MUC: The MUC lib is now metatable based. Cleaned up code, etc.
author | Waqas Hussain <waqas20@gmail.com> |
---|---|
date | Mon, 07 Sep 2009 20:11:13 +0500 |
parent | 1734:34ac9ba0aad6 |
child | 1736:98f833669d7f |
comparison
equal
deleted
inserted
replaced
1734:34ac9ba0aad6 | 1735:81406277279e |
---|---|
13 local jid_bare = require "util.jid".bare; | 13 local jid_bare = require "util.jid".bare; |
14 local st = require "util.stanza"; | 14 local st = require "util.stanza"; |
15 local log = require "util.logger".init("mod_muc"); | 15 local log = require "util.logger".init("mod_muc"); |
16 local multitable_new = require "util.multitable".new; | 16 local multitable_new = require "util.multitable".new; |
17 local t_insert, t_remove = table.insert, table.remove; | 17 local t_insert, t_remove = table.insert, table.remove; |
18 local setmetatable = setmetatable; | |
18 | 19 |
19 local muc_domain = nil; --module:get_host(); | 20 local muc_domain = nil; --module:get_host(); |
20 local history_length = 20; | 21 local history_length = 20; |
21 | 22 |
22 ------------ | 23 ------------ |
89 return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); | 90 return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); |
90 end -- TODO allow non-private rooms]] | 91 end -- TODO allow non-private rooms]] |
91 | 92 |
92 -- | 93 -- |
93 | 94 |
94 local function room_broadcast_presence(room, stanza, code, nick) | 95 local room_mt = {}; |
96 | |
97 local function room_mt:broadcast_presence(stanza, code, nick) | |
95 stanza = get_filtered_presence(stanza); | 98 stanza = get_filtered_presence(stanza); |
96 local data = room._participants[stanza.attr.from]; | 99 local data = self._participants[stanza.attr.from]; |
97 stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) | 100 stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) |
98 :tag("item", {affiliation=data.affiliation, role=data.role, nick=nick}):up(); | 101 :tag("item", {affiliation=data.affiliation, role=data.role, nick=nick}):up(); |
99 if code then | 102 if code then |
100 stanza:tag("status", {code=code}):up(); | 103 stanza:tag("status", {code=code}):up(); |
101 end | 104 end |
102 local me; | 105 local me; |
103 for occupant, o_data in pairs(room._participants) do | 106 for occupant, o_data in pairs(self._participants) do |
104 if occupant ~= stanza.attr.from then | 107 if occupant ~= stanza.attr.from then |
105 for jid in pairs(o_data.sessions) do | 108 for jid in pairs(o_data.sessions) do |
106 stanza.attr.to = jid; | 109 stanza.attr.to = jid; |
107 room:route_stanza(stanza); | 110 self:route_stanza(stanza); |
108 end | 111 end |
109 else | 112 else |
110 me = o_data; | 113 me = o_data; |
111 end | 114 end |
112 end | 115 end |
113 if me then | 116 if me then |
114 stanza:tag("status", {code='110'}); | 117 stanza:tag("status", {code='110'}); |
115 for jid in pairs(me.sessions) do | 118 for jid in pairs(me.sessions) do |
116 stanza.attr.to = jid; | 119 stanza.attr.to = jid; |
117 room:route_stanza(stanza); | 120 self:route_stanza(stanza); |
118 end | 121 end |
119 end | 122 end |
120 end | 123 end |
121 local function room_broadcast_message(room, stanza, historic) | 124 local function room_mt:broadcast_message(stanza, historic) |
122 for occupant, o_data in pairs(room._participants) do | 125 for occupant, o_data in pairs(self._participants) do |
123 for jid in pairs(o_data.sessions) do | 126 for jid in pairs(o_data.sessions) do |
124 stanza.attr.to = jid; | 127 stanza.attr.to = jid; |
125 room:route_stanza(stanza); | 128 self:route_stanza(stanza); |
126 end | 129 end |
127 end | 130 end |
128 if historic then -- add to history | 131 if historic then -- add to history |
129 local history = room._data['history']; | 132 local history = self._data['history']; |
130 if not history then history = {}; room._data['history'] = history; end | 133 if not history then history = {}; self._data['history'] = history; end |
131 -- stanza = st.clone(stanza); | 134 -- stanza = st.clone(stanza); |
132 stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203 | 135 stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203 |
133 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) | 136 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) |
134 t_insert(history, st.clone(st.preserialize(stanza))); | 137 t_insert(history, st.clone(st.preserialize(stanza))); |
135 while #history > history_length do t_remove(history, 1) end | 138 while #history > history_length do t_remove(history, 1) end |
136 end | 139 end |
137 end | 140 end |
138 | 141 |
139 | 142 |
140 local function room_send_occupant_list(room, to) | 143 function room_mt:send_occupant_list(to) |
141 local current_nick = room._jid_nick[to]; | 144 local current_nick = self._jid_nick[to]; |
142 for occupant, o_data in pairs(room._participants) do | 145 for occupant, o_data in pairs(self._participants) do |
143 if occupant ~= current_nick then | 146 if occupant ~= current_nick then |
144 local pres = get_filtered_presence(o_data.sessions[o_data.jid]); | 147 local pres = get_filtered_presence(o_data.sessions[o_data.jid]); |
145 pres.attr.to, pres.attr.from = to, occupant; | 148 pres.attr.to, pres.attr.from = to, occupant; |
146 pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) | 149 pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) |
147 :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up(); | 150 :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up(); |
148 room:route_stanza(pres); | 151 self:route_stanza(pres); |
149 end | 152 end |
150 end | 153 end |
151 end | 154 end |
152 local function room_send_history(room, to) | 155 function room_mt:send_history(to) |
153 local history = room._data['history']; -- send discussion history | 156 local history = self._data['history']; -- send discussion history |
154 if history then | 157 if history then |
155 for _, msg in ipairs(history) do | 158 for _, msg in ipairs(history) do |
156 msg = st.deserialize(msg); | 159 msg = st.deserialize(msg); |
157 msg.attr.to=to; | 160 msg.attr.to=to; |
158 room:route_stanza(msg); | 161 self:route_stanza(msg); |
159 end | 162 end |
160 end | 163 end |
161 if room._data['subject'] then | 164 if self._data['subject'] then |
162 room:route_stanza(st.message({type='groupchat', from=room.jid, to=to}):tag("subject"):text(room._data['subject'])); | 165 self:route_stanza(st.message({type='groupchat', from=self.jid, to=to}):tag("subject"):text(self._data['subject'])); |
163 end | 166 end |
164 end | 167 end |
165 | 168 |
166 local function room_get_disco_info(self, stanza) end | 169 local function room_get_disco_info(self, stanza) end |
167 local function room_get_disco_items(self, stanza) end | 170 local function room_get_disco_items(self, stanza) end |
168 local function room_set_subject(room, current_nick, subject) | 171 function room_mt:set_subject(current_nick, subject) |
169 -- TODO check nick's authority | 172 -- TODO check nick's authority |
170 if subject == "" then subject = nil; end | 173 if subject == "" then subject = nil; end |
171 room._data['subject'] = subject; | 174 self._data['subject'] = subject; |
172 local msg = st.message({type='groupchat', from=current_nick}) | 175 local msg = st.message({type='groupchat', from=current_nick}) |
173 :tag('subject'):text(subject):up(); | 176 :tag('subject'):text(subject):up(); |
174 room_broadcast_message(room, msg, false); | 177 self:broadcast_message(msg, false); |
175 return true; | 178 return true; |
176 end | 179 end |
177 | 180 |
178 local function room_handle_to_occupant(self, origin, stanza) -- PM, vCards, etc | 181 function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc |
179 local from, to = stanza.attr.from, stanza.attr.to; | 182 local from, to = stanza.attr.from, stanza.attr.to; |
180 local room = jid_bare(to); | 183 local room = jid_bare(to); |
181 local current_nick = self._jid_nick[from]; | 184 local current_nick = self._jid_nick[from]; |
182 local type = stanza.attr.type; | 185 local type = stanza.attr.type; |
183 log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); | 186 log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); |
186 local pr = get_filtered_presence(stanza); | 189 local pr = get_filtered_presence(stanza); |
187 pr.attr.from = current_nick; | 190 pr.attr.from = current_nick; |
188 if type == "error" then -- error, kick em out! | 191 if type == "error" then -- error, kick em out! |
189 if current_nick then | 192 if current_nick then |
190 log("debug", "kicking %s from %s", current_nick, room); | 193 log("debug", "kicking %s from %s", current_nick, room); |
191 room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error presence')); -- send unavailable | 194 self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) |
195 :tag('status'):text('This participant is kicked from the room because he sent an error presence')); -- send unavailable | |
192 end | 196 end |
193 elseif type == "unavailable" then -- unavailable | 197 elseif type == "unavailable" then -- unavailable |
194 if current_nick then | 198 if current_nick then |
195 log("debug", "%s leaving %s", current_nick, room); | 199 log("debug", "%s leaving %s", current_nick, room); |
196 local data = self._participants[current_nick]; | 200 local data = self._participants[current_nick]; |
197 data.role = 'none'; | 201 data.role = 'none'; |
198 room_broadcast_presence(self, pr); | 202 self:broadcast_presence(pr); |
199 self._participants[current_nick] = nil; | 203 self._participants[current_nick] = nil; |
200 self._jid_nick[from] = nil; | 204 self._jid_nick[from] = nil; |
201 end | 205 end |
202 elseif not type then -- available | 206 elseif not type then -- available |
203 if current_nick then | 207 if current_nick then |
204 --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence | 208 --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence |
205 if current_nick == to then -- simple presence | 209 if current_nick == to then -- simple presence |
206 log("debug", "%s broadcasted presence", current_nick); | 210 log("debug", "%s broadcasted presence", current_nick); |
207 self._participants[current_nick].sessions[from] = pr; | 211 self._participants[current_nick].sessions[from] = pr; |
208 room_broadcast_presence(self, pr); | 212 self:broadcast_presence(pr); |
209 else -- change nick | 213 else -- change nick |
210 if self._participants[to] then | 214 if self._participants[to] then |
211 log("debug", "%s couldn't change nick", current_nick); | 215 log("debug", "%s couldn't change nick", current_nick); |
212 origin.send(st.error_reply(stanza, "cancel", "conflict"):tag("x", {xmlns = "http://jabber.org/protocol/muc"})); | 216 origin.send(st.error_reply(stanza, "cancel", "conflict"):tag("x", {xmlns = "http://jabber.org/protocol/muc"})); |
213 else | 217 else |
214 local data = self._participants[current_nick]; | 218 local data = self._participants[current_nick]; |
215 local to_nick = select(3, jid_split(to)); | 219 local to_nick = select(3, jid_split(to)); |
216 if to_nick then | 220 if to_nick then |
217 log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); | 221 log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); |
218 local p = st.presence({type='unavailable', from=current_nick}); | 222 local p = st.presence({type='unavailable', from=current_nick}); |
219 room_broadcast_presence(self, p, '303', to_nick); | 223 self:broadcast_presence(p, '303', to_nick); |
220 self._participants[current_nick] = nil; | 224 self._participants[current_nick] = nil; |
221 self._participants[to] = data; | 225 self._participants[to] = data; |
222 self._jid_nick[from] = to; | 226 self._jid_nick[from] = to; |
223 pr.attr.from = to; | 227 pr.attr.from = to; |
224 self._participants[to].sessions[from] = pr; | 228 self._participants[to].sessions[from] = pr; |
225 room_broadcast_presence(self, pr); | 229 self:broadcast_presence(pr); |
226 else | 230 else |
227 --TODO malformed-jid | 231 --TODO malformed-jid |
228 end | 232 end |
229 end | 233 end |
230 end | 234 end |
231 --else -- possible rejoin | 235 --else -- possible rejoin |
232 -- log("debug", "%s had connection replaced", current_nick); | 236 -- log("debug", "%s had connection replaced", current_nick); |
233 -- handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('Replaced by new connection'):up()); -- send unavailable | 237 -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) |
234 -- handle_to_occupant(origin, stanza); -- resend available | 238 -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable |
239 -- self:handle_to_occupant(origin, stanza); -- resend available | |
235 --end | 240 --end |
236 else -- enter room | 241 else -- enter room |
237 local new_nick = to; | 242 local new_nick = to; |
238 if self._participants[to] then | 243 if self._participants[to] then |
239 new_nick = nil; | 244 new_nick = nil; |
251 if not data then -- new occupant | 256 if not data then -- new occupant |
252 data = {affiliation='none', role='participant', jid=from, sessions={[from]=get_filtered_presence(stanza)}}; | 257 data = {affiliation='none', role='participant', jid=from, sessions={[from]=get_filtered_presence(stanza)}}; |
253 end | 258 end |
254 self._participants[to] = data; | 259 self._participants[to] = data; |
255 self._jid_nick[from] = to; | 260 self._jid_nick[from] = to; |
256 room_send_occupant_list(self, from); | 261 self:send_occupant_list(from); |
257 pr.attr.from = to; | 262 pr.attr.from = to; |
258 room_broadcast_presence(self, pr); | 263 self:broadcast_presence(pr); |
259 room_send_history(self, from); | 264 self:send_history(from); |
260 end | 265 end |
261 end | 266 end |
262 elseif type ~= 'result' then -- bad type | 267 elseif type ~= 'result' then -- bad type |
263 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? | 268 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? |
264 end | 269 end |
266 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); | 271 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); |
267 elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM | 272 elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM |
268 origin.send(st.error_reply(stanza, "modify", "bad-request")); | 273 origin.send(st.error_reply(stanza, "modify", "bad-request")); |
269 elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then | 274 elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then |
270 log("debug", "%s kicked from %s for sending an error message", current_nick, room); | 275 log("debug", "%s kicked from %s for sending an error message", current_nick, room); |
271 room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable | 276 self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable |
272 else -- private stanza | 277 else -- private stanza |
273 local o_data = self._participants[to]; | 278 local o_data = self._participants[to]; |
274 if o_data then | 279 if o_data then |
275 log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); | 280 log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); |
276 local jid = o_data.jid; | 281 local jid = o_data.jid; |
281 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); | 286 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); |
282 end | 287 end |
283 end | 288 end |
284 end | 289 end |
285 | 290 |
286 local function room_handle_to_room(self, origin, stanza) -- presence changes and groupchat messages, along with disco/etc | 291 function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc |
287 local type = stanza.attr.type; | 292 local type = stanza.attr.type; |
288 if stanza.name == "iq" and type == "get" then -- disco requests | 293 if stanza.name == "iq" and type == "get" then -- disco requests |
289 local xmlns = stanza.tags[1].attr.xmlns; | 294 local xmlns = stanza.tags[1].attr.xmlns; |
290 if xmlns == "http://jabber.org/protocol/disco#info" then | 295 if xmlns == "http://jabber.org/protocol/disco#info" then |
291 origin.send(room_get_disco_info(self, stanza)); | 296 origin.send(room_get_disco_info(self, stanza)); |
305 stanza.attr.from = current_nick; | 310 stanza.attr.from = current_nick; |
306 local subject = getText(stanza, {"subject"}); | 311 local subject = getText(stanza, {"subject"}); |
307 if subject then | 312 if subject then |
308 self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza | 313 self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza |
309 else | 314 else |
310 room_broadcast_message(self, stanza, true); | 315 self:broadcast_message(stanza, true); |
311 end | 316 end |
312 end | 317 end |
313 elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick | 318 elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick |
314 local to = stanza.attr.to; | 319 local to = stanza.attr.to; |
315 local current_nick = self._jid_nick[stanza.attr.from]; | 320 local current_nick = self._jid_nick[stanza.attr.from]; |
316 if current_nick then | 321 if current_nick then |
317 stanza.attr.to = current_nick; | 322 stanza.attr.to = current_nick; |
318 room_handle_to_occupant(self, origin, stanza); | 323 self:handle_to_occupant(origin, stanza); |
319 stanza.attr.to = to; | 324 stanza.attr.to = to; |
320 elseif type ~= "error" and type ~= "result" then | 325 elseif type ~= "error" and type ~= "result" then |
321 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); | 326 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
322 end | 327 end |
323 elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] | 328 elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] |
334 if type == "error" or type == "result" then return; end | 339 if type == "error" or type == "result" then return; end |
335 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); | 340 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
336 end | 341 end |
337 end | 342 end |
338 | 343 |
339 local function room_handle_stanza(self, origin, stanza) | 344 function room_mt:handle_stanza(origin, stanza) |
340 local to_node, to_host, to_resource = jid_split(stanza.attr.to); | 345 local to_node, to_host, to_resource = jid_split(stanza.attr.to); |
341 if to_resource then | 346 if to_resource then |
342 room_handle_to_occupant(self, origin, stanza); | 347 self:handle_to_occupant(origin, stanza); |
343 else | 348 else |
344 room_handle_to_room(self, origin, stanza); | 349 self:handle_to_room(origin, stanza); |
345 end | 350 end |
346 end | 351 end |
352 | |
353 function room_mt:route_stanza(stanza) end -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end | |
347 | 354 |
348 module "muc" | 355 module "muc" |
349 | 356 |
350 function new_room(jid) | 357 function new_room(jid) |
351 return { | 358 return setmetatable({ |
352 jid = jid; | 359 jid = jid; |
353 handle_stanza = room_handle_stanza; | |
354 set_subject = room_set_subject; | |
355 route_stanza = function(room, stanza) end; -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end | |
356 _jid_nick = {}; | 360 _jid_nick = {}; |
357 _participants = {}; | 361 _participants = {}; |
358 _data = {}; | 362 _data = {}; |
359 } | 363 }, room_mt); |
360 end | 364 end |
361 | 365 |
362 return _M; | 366 return _M; |
363 | 367 |
364 --[[function get_disco_info(stanza) | 368 --[[function get_disco_info(stanza) |