Mercurial > prosody-hg
comparison plugins/mod_muc.lua @ 666:27f76695f43b
Initial mod_muc: XEP-0045: Multi-User Chat
| author | Waqas Hussain <waqas20@gmail.com> |
|---|---|
| date | Wed, 31 Dec 2008 10:16:42 +0500 |
| parents | |
| children | 50072761e02d |
comparison
equal
deleted
inserted
replaced
| 665:09e0e9c722a3 | 666:27f76695f43b |
|---|---|
| 1 | |
| 2 | |
| 3 local register_component = require "core.componentmanager".register_component; | |
| 4 local jid_split = require "util.jid".split; | |
| 5 local jid_bare = require "util.jid".bare; | |
| 6 local st = require "util.stanza"; | |
| 7 local log = require "util.logger".init("mod_muc"); | |
| 8 local multitable_new = require "util.multitable".new; | |
| 9 | |
| 10 local muc_domain = "conference."..module:get_host(); | |
| 11 local muc_name = "MUCMUCMUC!!!"; | |
| 12 | |
| 13 local rooms = multitable_new(); | |
| 14 local jid_nick = multitable_new(); | |
| 15 local rooms_info = multitable_new(); | |
| 16 | |
| 17 local component; | |
| 18 | |
| 19 function getUsingPath(stanza, path, getText) | |
| 20 local tag = stanza; | |
| 21 for _, name in ipairs(path) do | |
| 22 if type(tag) ~= 'table' then return; end | |
| 23 tag = tag:child_with_name(name); | |
| 24 end | |
| 25 if tag and getText then tag = table.concat(tag); end | |
| 26 return tag; | |
| 27 end | |
| 28 function getTag(stanza, path) return getUsingPath(stanza, path); end | |
| 29 function getText(stanza, path) return getUsingPath(stanza, path, true); end | |
| 30 | |
| 31 function get_disco_info(stanza) | |
| 32 return st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info") | |
| 33 :tag("identity", {category='conference', type='text', name=muc_name}):up() | |
| 34 :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply | |
| 35 end | |
| 36 function get_disco_items(stanza) | |
| 37 local reply = st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); | |
| 38 for room in pairs(rooms_info:get()) do | |
| 39 reply:tag("item", {jid=room, name=rooms_info:get(room, "name")}):up(); | |
| 40 end | |
| 41 return reply; -- TODO cache disco reply | |
| 42 end | |
| 43 function get_room_disco_info(stanza) | |
| 44 return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info") | |
| 45 :tag("identity", {category='conference', type='text', name=rooms_info:get(stanza.attr.to, "name")}):up() | |
| 46 :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply | |
| 47 end | |
| 48 function get_room_disco_items(stanza) | |
| 49 return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); | |
| 50 end -- TODO allow non-private rooms | |
| 51 | |
| 52 function broadcast_presence(type, from, room, code) | |
| 53 local data = rooms:get(room, from); | |
| 54 local stanza = st.presence({type=type, from=from}) | |
| 55 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) | |
| 56 :tag("item", {affiliation=data.affiliation, role=data.role}):up(); | |
| 57 if code then | |
| 58 stanza:tag("status", {code=code}):up(); | |
| 59 end | |
| 60 local me; | |
| 61 local r = rooms:get(room); | |
| 62 if r then | |
| 63 for occupant, o_data in pairs(r) do | |
| 64 if occupant ~= from then | |
| 65 stanza.attr.to = o_data.jid; | |
| 66 core_route_stanza(component, stanza); | |
| 67 else | |
| 68 me = o_data.jid; | |
| 69 end | |
| 70 end | |
| 71 end | |
| 72 if me then | |
| 73 stanza:tag("status", {code='110'}); | |
| 74 stanza.attr.to = me; | |
| 75 core_route_stanza(component, stanza); | |
| 76 end | |
| 77 end | |
| 78 function broadcast_message(from, room, subject, body) | |
| 79 local stanza = st.message({type='groupchat', from=from}); | |
| 80 if subject then stanza:tag('subject'):text(subject):up(); end | |
| 81 if body then stanza:tag('body'):text(body):up(); end | |
| 82 local r = rooms:get(room); | |
| 83 if r then | |
| 84 for occupant, o_data in pairs(r) do | |
| 85 stanza.attr.to = o_data.jid; | |
| 86 core_route_stanza(component, stanza); | |
| 87 end | |
| 88 end | |
| 89 end | |
| 90 | |
| 91 function handle_to_occupant(origin, stanza) -- PM, vCards, etc | |
| 92 local from, to = stanza.attr.from, stanza.attr.to; | |
| 93 local room = jid_bare(to); | |
| 94 local current_nick = jid_nick:get(from, room); | |
| 95 local type = stanza.attr.type; | |
| 96 if stanza.name == "presence" then | |
| 97 if type == "error" then -- error, kick em out! | |
| 98 local data = rooms:get(room, to); | |
| 99 data.role = 'none'; | |
| 100 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> | |
| 101 rooms:remove(room, to); | |
| 102 jid_nick:remove(from, room); | |
| 103 elseif type == "unavailable" then -- unavailable | |
| 104 if current_nick == to then | |
| 105 local data = rooms:get(room, to); | |
| 106 data.role = 'none'; | |
| 107 broadcast_presence('unavailable', to, room); | |
| 108 rooms:remove(room, to); | |
| 109 jid_nick:remove(from, room); | |
| 110 end -- TODO else do nothing? | |
| 111 elseif not type then -- available | |
| 112 if current_nick then | |
| 113 if current_nick == to then -- simple presence | |
| 114 -- TODO broadcast | |
| 115 else -- change nick | |
| 116 if rooms:get(room, to) then | |
| 117 origin.send(st.error_reply(stanza, "cancel", "conflict")); | |
| 118 else | |
| 119 local data = rooms:get(room, current_nick); | |
| 120 broadcast_presence('unavailable', current_nick, room, '303'); | |
| 121 rooms:remove(room, current_nick); | |
| 122 rooms:set(room, to, data); | |
| 123 jid_nick:set(from, room, to); | |
| 124 broadcast_presence(nil, to, room); | |
| 125 end | |
| 126 end | |
| 127 else -- enter room | |
| 128 if rooms:get(room, to) then | |
| 129 origin.send(st.error_reply(stanza, "cancel", "conflict")); | |
| 130 else | |
| 131 local data = {affiliation='none', role='participant', jid=from}; | |
| 132 rooms:set(room, to, data); | |
| 133 jid_nick:set(from, room, to); | |
| 134 local r = rooms:get(room); | |
| 135 if r then | |
| 136 for occupant, o_data in pairs(r) do | |
| 137 if occupant ~= from then | |
| 138 local pres = st.presence({to=from, from=occupant}) | |
| 139 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) | |
| 140 :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up(); | |
| 141 core_route_stanza(component, pres); | |
| 142 end | |
| 143 end | |
| 144 end | |
| 145 broadcast_presence(nil, to, room); | |
| 146 end | |
| 147 end | |
| 148 elseif type ~= 'result' then -- bad type | |
| 149 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? | |
| 150 end | |
| 151 elseif stanza.name == "message" and type == "groupchat" then | |
| 152 -- groupchat messages not allowed in PM | |
| 153 origin.send(st.error_reply(stanza, "modify", "bad-request")); | |
| 154 else | |
| 155 origin.send(st.error_reply(stanza, "cancel", "not-implemented", "Private stanzas not implemented")); -- TODO route private stanza | |
| 156 end | |
| 157 end | |
| 158 | |
| 159 function handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc | |
| 160 local type = stanza.attr.type; | |
| 161 if stanza.name == "iq" and type == "get" then -- disco requests | |
| 162 local xmlns = stanza.tags[1].attr.xmlns; | |
| 163 if xmlns == "http://jabber.org/protocol/disco#info" then | |
| 164 origin.send(get_room_disco_info(stanza)); | |
| 165 elseif xmlns == "http://jabber.org/protocol/disco#items" then | |
| 166 origin.send(get_room_disco_items(stanza)); | |
| 167 else | |
| 168 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); | |
| 169 end | |
| 170 elseif stanza.name == "message" and type == "groupchat" then | |
| 171 local from, to = stanza.attr.from, stanza.attr.to; | |
| 172 local room = jid_bare(to); | |
| 173 local current_nick = jid_nick:get(from, room); | |
| 174 if not current_nick then -- not in room | |
| 175 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); | |
| 176 else | |
| 177 broadcast_message(current_nick, room, getText(stanza, {"subject"}), getText(stanza, {"body"})); | |
| 178 end | |
| 179 else | |
| 180 if type == "error" or type == "result" then return; end | |
| 181 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); | |
| 182 end | |
| 183 end | |
| 184 | |
| 185 function handle_to_domain(origin, stanza) | |
| 186 local type = stanza.attr.type; | |
| 187 if type == "error" or type == "result" then return; end | |
| 188 if stanza.name == "iq" and type == "get" then | |
| 189 local xmlns = stanza.tags[1].attr.xmlns; | |
| 190 if xmlns == "http://jabber.org/protocol/disco#info" then | |
| 191 origin.send(get_disco_info(stanza)); | |
| 192 elseif xmlns == "http://jabber.org/protocol/disco#items" then | |
| 193 origin.send(get_disco_items(stanza)); | |
| 194 else | |
| 195 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc | |
| 196 end | |
| 197 else | |
| 198 origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); | |
| 199 end | |
| 200 end | |
| 201 | |
| 202 component = register_component(muc_domain, function(origin, stanza) | |
| 203 local to_node, to_host, to_resource = jid_split(stanza.attr.to); | |
| 204 if stanza.name == "presence" and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" then | |
| 205 if type == "error" or type == "result" then return; end | |
| 206 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME what's appropriate? | |
| 207 elseif to_resource and not to_node then | |
| 208 if type == "error" or type == "result" then return; end | |
| 209 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource | |
| 210 elseif to_resource then | |
| 211 handle_to_occupant(origin, stanza); | |
| 212 elseif to_node then | |
| 213 handle_to_room(origin, stanza) | |
| 214 else -- to the main muc domain | |
| 215 if type == "error" or type == "result" then return; end | |
| 216 handle_to_domain(origin, stanza); | |
| 217 end | |
| 218 end); |
