Software /
code /
prosody
Comparison
plugins/mod_muc.lua @ 810:09d6b5fadc84
MUC: Presence and message stanzas now fully work (status messages, xhtml-im, etc)
author | Waqas Hussain <waqas20@gmail.com> |
---|---|
date | Mon, 16 Feb 2009 19:39:10 +0500 |
parent | 782:6f9b2a9d6d45 |
child | 811:863046d84b56 |
comparison
equal
deleted
inserted
replaced
809:28d6515f5b7b | 810:09d6b5fadc84 |
---|---|
39 rooms_info:set(room, datamanager.store(room, muc_domain, 'rooms') or nil); | 39 rooms_info:set(room, datamanager.store(room, muc_domain, 'rooms') or nil); |
40 end | 40 end |
41 | 41 |
42 local component; | 42 local component; |
43 | 43 |
44 function filter_xmlns_from_array(array, filters) | |
45 local count = 0; | |
46 for i=#array,1,-1 do | |
47 local attr = array[i].attr; | |
48 if filters[attr and attr.xmlns] then | |
49 t_remove(array, i); | |
50 count = count + 1; | |
51 end | |
52 end | |
53 return count; | |
54 end | |
55 function filter_xmlns_from_stanza(stanza, filters) | |
56 if filters then | |
57 if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then | |
58 return stanza, filter_xmlns_from_array(stanza, filters); | |
59 end | |
60 end | |
61 return stanza, 0; | |
62 end | |
63 local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; | |
64 function get_filtered_presence(stanza) | |
65 return filter_xmlns_from_stanza(st.deserialize(st.preserialize(stanza)), presence_filters); | |
66 end | |
44 function getUsingPath(stanza, path, getText) | 67 function getUsingPath(stanza, path, getText) |
45 local tag = stanza; | 68 local tag = stanza; |
46 for _, name in ipairs(path) do | 69 for _, name in ipairs(path) do |
47 if type(tag) ~= 'table' then return; end | 70 if type(tag) ~= 'table' then return; end |
48 tag = tag:child_with_name(name); | 71 tag = tag:child_with_name(name); |
91 function set_subject(current_nick, room, subject) | 114 function set_subject(current_nick, room, subject) |
92 -- TODO check nick's authority | 115 -- TODO check nick's authority |
93 if subject == "" then subject = nil; end | 116 if subject == "" then subject = nil; end |
94 rooms_info:set(room, 'subject', subject); | 117 rooms_info:set(room, 'subject', subject); |
95 save_room(); | 118 save_room(); |
96 broadcast_message(current_nick, room, subject or "", nil); | 119 local msg = st.message({type='groupchat', from=from}) |
120 :tag('subject'):text(subject):up(); | |
121 broadcast_message_stanza(room, msg, false); | |
122 --broadcast_message(current_nick, room, subject or "", nil); | |
97 return true; | 123 return true; |
98 end | 124 end |
99 | 125 |
100 function broadcast_presence(type, from, room, code, newnick) | 126 function broadcast_presence(type, from, room, code, newnick) |
101 local data = rooms:get(room, from); | 127 local data = rooms:get(room, from); |
142 t_insert(history, st.preserialize(stanza)); | 168 t_insert(history, st.preserialize(stanza)); |
143 while #history > history_length do t_remove(history, 1) end | 169 while #history > history_length do t_remove(history, 1) end |
144 end | 170 end |
145 end | 171 end |
146 end | 172 end |
173 function broadcast_message_stanza(room, stanza, historic) | |
174 local r = rooms:get(room); | |
175 if r then | |
176 for occupant, o_data in pairs(r) do | |
177 for jid in pairs(o_data.sessions) do | |
178 stanza.attr.to = jid; | |
179 core_route_stanza(component, stanza); | |
180 end | |
181 end | |
182 if historic then -- add to history | |
183 local history = rooms_info:get(room, 'history'); | |
184 if not history then history = {}; rooms_info:set(room, 'history', history); end | |
185 -- stanza = st.deserialize(st.preserialize(stanza)); | |
186 stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203 | |
187 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) | |
188 t_insert(history, st.preserialize(stanza)); | |
189 while #history > history_length do t_remove(history, 1) end | |
190 end | |
191 end | |
192 end | |
193 function broadcast_presence_stanza(room, stanza, code, nick) | |
194 stanza = get_filtered_presence(stanza); | |
195 local data = rooms:get(room, stanza.attr.from); | |
196 stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) | |
197 :tag("item", {affiliation=data.affiliation, role=data.role, nick=nick}):up(); | |
198 if code then | |
199 stanza:tag("status", {code=code}):up(); | |
200 end | |
201 local me; | |
202 local r = rooms:get(room); | |
203 if r then | |
204 for occupant, o_data in pairs(r) do | |
205 if occupant ~= stanza.attr.from then | |
206 for jid in pairs(o_data.sessions) do | |
207 stanza.attr.to = jid; | |
208 core_route_stanza(component, stanza); | |
209 end | |
210 else | |
211 me = o_data; | |
212 end | |
213 end | |
214 end | |
215 if me then | |
216 stanza:tag("status", {code='110'}); | |
217 for jid in pairs(me.sessions) do | |
218 stanza.attr.to = jid; | |
219 core_route_stanza(component, stanza); | |
220 end | |
221 end | |
222 end | |
147 | 223 |
148 function handle_to_occupant(origin, stanza) -- PM, vCards, etc | 224 function handle_to_occupant(origin, stanza) -- PM, vCards, etc |
149 local from, to = stanza.attr.from, stanza.attr.to; | 225 local from, to = stanza.attr.from, stanza.attr.to; |
150 local room = jid_bare(to); | 226 local room = jid_bare(to); |
151 local current_nick = jid_nick:get(from, room); | 227 local current_nick = jid_nick:get(from, room); |
152 local type = stanza.attr.type; | 228 local type = stanza.attr.type; |
153 if stanza.name == "presence" then | 229 if stanza.name == "presence" then |
230 local pr = get_filtered_presence(stanza); | |
231 pr.attr.from = to; | |
154 if type == "error" then -- error, kick em out! | 232 if type == "error" then -- error, kick em out! |
155 local data = rooms:get(room, to); | 233 if current_nick then |
156 data.role = 'none'; | |
157 broadcast_presence('unavailable', to, room); -- TODO also add <status>This participant is kicked from the room because he sent an error presence: badformed error stanza</status> | |
158 rooms:remove(room, to); | |
159 jid_nick:remove(from, room); | |
160 elseif type == "unavailable" then -- unavailable | |
161 if current_nick == to then | |
162 local data = rooms:get(room, to); | 234 local data = rooms:get(room, to); |
163 data.role = 'none'; | 235 data.role = 'none'; |
164 broadcast_presence('unavailable', to, room); | 236 local pr = st.presence({type='unavailable', from=current_nick}):tag('status'):text('This participant is kicked from the room because he sent an error presence'):up() |
237 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) | |
238 :tag("item", {affiliation=data.affiliation, role=data.role}):up(); | |
239 broadcast_presence_stanza(room, pr); | |
240 --broadcast_presence('unavailable', to, room); -- TODO also add <status>This participant is kicked from the room because he sent an error presence: badformed error stanza</status> | |
165 rooms:remove(room, to); | 241 rooms:remove(room, to); |
166 jid_nick:remove(from, room); | 242 jid_nick:remove(from, room); |
167 end -- TODO else do nothing? | 243 end |
244 elseif type == "unavailable" then -- unavailable | |
245 if current_nick then | |
246 local data = rooms:get(room, to); | |
247 data.role = 'none'; | |
248 broadcast_presence_stanza(room, pr); | |
249 --broadcast_presence('unavailable', to, room); | |
250 rooms:remove(room, to); | |
251 jid_nick:remove(from, room); | |
252 end | |
168 elseif not type then -- available | 253 elseif not type then -- available |
169 if current_nick then | 254 if current_nick then |
170 if current_nick == to then -- simple presence | 255 if current_nick == to then -- simple presence |
171 -- TODO broadcast | 256 broadcast_presence_stanza(room, pr); |
257 -- FIXME check if something was filtered. if it was, then user may be rejoining | |
172 else -- change nick | 258 else -- change nick |
173 if rooms:get(room, to) then | 259 if rooms:get(room, to) then |
174 origin.send(st.error_reply(stanza, "cancel", "conflict")); | 260 origin.send(st.error_reply(stanza, "cancel", "conflict")); |
175 else | 261 else |
176 local data = rooms:get(room, current_nick); | 262 local data = rooms:get(room, current_nick); |
177 local to_nick = select(3, jid_split(to)); | 263 local to_nick = select(3, jid_split(to)); |
178 if to_nick then | 264 if to_nick then |
179 broadcast_presence('unavailable', current_nick, room, '303', to_nick); | 265 local p = st.presence({type='unavailable', from=current_nick}); |
266 --[[:tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) | |
267 :tag('item', {affiliation=data.affiliation, role=data.role, nick=to_nick}):up() | |
268 :tag('status', {code='303'});]] | |
269 broadcast_presence_stanza(room, p, '303', to_nick); | |
270 --broadcast_presence('unavailable', current_nick, room, '303', to_nick); | |
180 rooms:remove(room, current_nick); | 271 rooms:remove(room, current_nick); |
181 rooms:set(room, to, data); | 272 rooms:set(room, to, data); |
182 jid_nick:set(from, room, to); | 273 jid_nick:set(from, room, to); |
183 broadcast_presence(nil, to, room, nil); | 274 broadcast_presence_stanza(room, pr); |
275 --broadcast_presence(nil, to, room, nil); | |
184 else | 276 else |
185 --TODO: malformed-jid | 277 --TODO malformed-jid |
186 end | 278 end |
187 end | 279 end |
188 end | 280 end |
189 else -- enter room | 281 else -- enter room |
282 local new_nick = to; | |
190 if rooms:get(room, to) then | 283 if rooms:get(room, to) then |
284 new_nick = nil; | |
285 end | |
286 if not new_nick then | |
191 origin.send(st.error_reply(stanza, "cancel", "conflict")); | 287 origin.send(st.error_reply(stanza, "cancel", "conflict")); |
192 else | 288 else |
193 local data; | 289 local data; |
194 if not rooms:get(room) and not rooms_info:get(room) then -- new room | 290 if not rooms:get(room) and not rooms_info:get(room) then -- new room |
195 data = {affiliation='owner', role='moderator', jid=from}; | 291 data = {affiliation='owner', role='moderator', jid=from, sessions={[from]=get_filtered_presence(stanza)}}; |
196 end | 292 end |
197 if not data then -- new occupant | 293 if not data then -- new occupant |
198 data = {affiliation='none', role='participant', jid=from}; | 294 data = {affiliation='none', role='participant', jid=from, sessions={[from]=get_filtered_presence(stanza)}}; |
199 end | 295 end |
200 rooms:set(room, to, data); | 296 rooms:set(room, to, data); |
201 jid_nick:set(from, room, to); | 297 jid_nick:set(from, room, to); |
202 local r = rooms:get(room); | 298 local r = rooms:get(room); |
203 if r then | 299 if r then |
204 for occupant, o_data in pairs(r) do | 300 for occupant, o_data in pairs(r) do |
205 if occupant ~= from then | 301 if occupant ~= from then |
206 local pres = st.presence({to=from, from=occupant}) | 302 local pres = get_filtered_presence(o_data.sessions[o_data.jid]); |
303 pres.attr.to, pres.attr.from = from, occupant; | |
304 pres | |
305 --local pres = st.presence({to=from, from=occupant}) | |
207 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) | 306 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) |
208 :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up(); | 307 :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up(); |
209 core_route_stanza(component, pres); | 308 core_route_stanza(component, pres); |
210 end | 309 end |
211 end | 310 end |
212 end | 311 end |
213 broadcast_presence(nil, to, room); | 312 broadcast_presence_stanza(room, pr); |
313 --broadcast_presence(nil, to, room); | |
214 local history = rooms_info:get(room, 'history'); -- send discussion history | 314 local history = rooms_info:get(room, 'history'); -- send discussion history |
215 if history then | 315 if history then |
216 for _, msg in ipairs(history) do | 316 for _, msg in ipairs(history) do |
217 msg = st.deserialize(msg); | 317 msg = st.deserialize(msg); |
218 msg.attr.to=from; | 318 msg.attr.to=from; |
251 local room = jid_bare(to); | 351 local room = jid_bare(to); |
252 local current_nick = jid_nick:get(from, room); | 352 local current_nick = jid_nick:get(from, room); |
253 if not current_nick then -- not in room | 353 if not current_nick then -- not in room |
254 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); | 354 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); |
255 else | 355 else |
356 local from = stanza.attr.from; | |
357 stanza.attr.from = current_nick; | |
256 local subject = getText(stanza, {"subject"}); | 358 local subject = getText(stanza, {"subject"}); |
257 if subject then | 359 if subject then |
258 set_subject(current_nick, room, subject); | 360 set_subject(current_nick, room, subject); -- TODO use broadcast_message_stanza |
259 else | 361 else |
260 broadcast_message(current_nick, room, nil, getText(stanza, {"body"})); | 362 --broadcast_message(current_nick, room, nil, getText(stanza, {"body"})); |
261 -- TODO add to discussion history | 363 broadcast_message_stanza(room, stanza, true); |
262 end | 364 end |
365 end | |
366 elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick | |
367 local to = stanza.attr.to; | |
368 local current_nick = jid_nick:get(stanza.attr.from, to); | |
369 if current_nick then | |
370 stanza.attr.to = current_nick; | |
371 handle_to_occupant(origin, stanza); | |
372 stanza.attr.to = to; | |
373 else | |
374 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); | |
263 end | 375 end |
264 else | 376 else |
265 if type == "error" or type == "result" then return; end | 377 if type == "error" or type == "result" then return; end |
266 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); | 378 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); |
267 end | 379 end |
284 end | 396 end |
285 end | 397 end |
286 | 398 |
287 register_component(muc_domain, function(origin, stanza) | 399 register_component(muc_domain, function(origin, stanza) |
288 local to_node, to_host, to_resource = jid_split(stanza.attr.to); | 400 local to_node, to_host, to_resource = jid_split(stanza.attr.to); |
289 if stanza.name == "presence" and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then | 401 if to_resource and not to_node then |
290 if type == "error" or type == "result" then return; end | |
291 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME what's appropriate? | |
292 elseif to_resource and not to_node then | |
293 if type == "error" or type == "result" then return; end | 402 if type == "error" or type == "result" then return; end |
294 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource | 403 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource |
295 elseif to_resource then | 404 elseif to_resource then |
296 handle_to_occupant(origin, stanza); | 405 handle_to_occupant(origin, stanza); |
297 elseif to_node then | 406 elseif to_node then |