plugins/muc/muc.lib.lua

changeset 1735
81406277279e
parent 1734
34ac9ba0aad6
child 1736
98f833669d7f
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)

mercurial