Mercurial > prosody-hg
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 |
| parents | 6f9b2a9d6d45 |
| children | 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 |
