Software /
code /
prosody
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 |
child | 668: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); |