comparison mod_ircd/verse/verse.lua @ 491:5b3db688213d

mod_ircd: Fixed nick change logic (thanks mva), so that the self nick-change "flag" is removed properly, improved the logic to use verse's room_mt:change_nick (thanks Zash) yet to be pushed into main, added squished verse with the meta method included.
author Marco Cirillo <maranda@lightwitch.org>
date Fri, 02 Dec 2011 20:53:09 +0000
parents
children
comparison
equal deleted inserted replaced
490:00b77a9f2d5f 491:5b3db688213d
1 package.preload['verse.plugins.presence'] = (function (...)
2 function verse.plugins.presence(stream)
3 stream.last_presence = nil;
4
5 stream:hook("presence-out", function (presence)
6 if not presence.attr.to then
7 stream.last_presence = presence; -- Cache non-directed presence
8 end
9 end, 1);
10
11 function stream:resend_presence()
12 if last_presence then
13 stream:send(last_presence);
14 end
15 end
16
17 function stream:set_status(opts)
18 local p = verse.presence();
19 if type(opts) == "table" then
20 if opts.show then
21 p:tag("show"):text(opts.show):up();
22 end
23 if opts.prio then
24 p:tag("priority"):text(tostring(opts.prio)):up();
25 end
26 if opts.msg then
27 p:tag("status"):text(opts.msg):up();
28 end
29 end
30 -- TODO maybe use opts as prio if it's a int,
31 -- or as show or status if it's a string?
32
33 stream:send(p);
34 end
35 end
36 end)
37 package.preload['verse.plugins.groupchat'] = (function (...)
38 local events = require "events";
39
40 local room_mt = {};
41 room_mt.__index = room_mt;
42
43 local xmlns_delay = "urn:xmpp:delay";
44 local xmlns_muc = "http://jabber.org/protocol/muc";
45
46 function verse.plugins.groupchat(stream)
47 stream:add_plugin("presence")
48 stream.rooms = {};
49
50 stream:hook("stanza", function (stanza)
51 local room_jid = jid.bare(stanza.attr.from);
52 if not room_jid then return end
53 local room = stream.rooms[room_jid]
54 if not room and stanza.attr.to and room_jid then
55 room = stream.rooms[stanza.attr.to.." "..room_jid]
56 end
57 if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end
58 if room then
59 local nick = select(3, jid.split(stanza.attr.from));
60 local body = stanza:get_child_text("body");
61 local delay = stanza:get_child("delay", xmlns_delay);
62 local event = {
63 room_jid = room_jid;
64 room = room;
65 sender = room.occupants[nick];
66 nick = nick;
67 body = body;
68 stanza = stanza;
69 delay = (delay and delay.attr.stamp);
70 };
71 local ret = room:event(stanza.name, event);
72 return ret or (stanza.name == "message") or nil;
73 end
74 end, 500);
75
76 function stream:join_room(jid, nick, opts)
77 if not nick then
78 return false, "no nickname supplied"
79 end
80 opts = opts or {};
81 local room = setmetatable({
82 stream = stream, jid = jid, nick = nick,
83 subject = nil,
84 occupants = {},
85 opts = opts,
86 events = events.new()
87 }, room_mt);
88 if opts.source then
89 self.rooms[opts.source.." "..jid] = room;
90 else
91 self.rooms[jid] = room;
92 end
93 local occupants = room.occupants;
94 room:hook("presence", function (presence)
95 local nick = presence.nick or nick;
96 if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then
97 occupants[nick] = {
98 nick = nick;
99 jid = presence.stanza.attr.from;
100 presence = presence.stanza;
101 };
102 local x = presence.stanza:get_child("x", xmlns_muc .. "#user");
103 if x then
104 local x_item = x:get_child("item");
105 if x_item and x_item.attr then
106 occupants[nick].real_jid = x_item.attr.jid;
107 occupants[nick].affiliation = x_item.attr.affiliation;
108 occupants[nick].role = x_item.attr.role;
109 end
110 --TODO Check for status 100?
111 end
112 if nick == room.nick then
113 room.stream:event("groupchat/joined", room);
114 else
115 room:event("occupant-joined", occupants[nick]);
116 end
117 elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then
118 if nick == room.nick then
119 room.stream:event("groupchat/left", room);
120 if room.opts.source then
121 self.rooms[room.opts.source.." "..jid] = nil;
122 else
123 self.rooms[jid] = nil;
124 end
125 else
126 occupants[nick].presence = presence.stanza;
127 room:event("occupant-left", occupants[nick]);
128 occupants[nick] = nil;
129 end
130 end
131 end);
132 room:hook("message", function(event)
133 local subject = event.stanza:get_child_text("subject");
134 if not subject then return end
135 subject = #subject > 0 and subject or nil;
136 if subject ~= room.subject then
137 local old_subject = room.subject;
138 room.subject = subject;
139 return room:event("subject-changed", { from = old_subject, to = subject, by = event.sender, event = event });
140 end
141 end, 2000);
142 local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset();
143 self:event("pre-groupchat/joining", join_st);
144 room:send(join_st)
145 self:event("groupchat/joining", room);
146 return room;
147 end
148
149 stream:hook("presence-out", function(presence)
150 if not presence.attr.to then
151 for _, room in pairs(stream.rooms) do
152 room:send(presence);
153 end
154 presence.attr.to = nil;
155 end
156 end);
157 end
158
159 function room_mt:send(stanza)
160 if stanza.name == "message" and not stanza.attr.type then
161 stanza.attr.type = "groupchat";
162 end
163 if stanza.name == "presence" then
164 stanza.attr.to = self.jid .."/"..self.nick;
165 end
166 if stanza.attr.type == "groupchat" or not stanza.attr.to then
167 stanza.attr.to = self.jid;
168 end
169 if self.opts.source then
170 stanza.attr.from = self.opts.source
171 end
172 self.stream:send(stanza);
173 end
174
175 function room_mt:send_message(text)
176 self:send(verse.message():tag("body"):text(text));
177 end
178
179 function room_mt:set_subject(text)
180 self:send(verse.message():tag("subject"):text(text));
181 end
182
183 function room_mt:change_nick(new)
184 self.nick = new;
185 self:send(verse.presence());
186 end
187
188 function room_mt:leave(message)
189 self.stream:event("groupchat/leaving", self);
190 self:send(verse.presence({type="unavailable"}));
191 end
192
193 function room_mt:admin_set(nick, what, value, reason)
194 self:send(verse.iq({type="set"})
195 :query(xmlns_muc .. "#admin")
196 :tag("item", {nick = nick, [what] = value})
197 :tag("reason"):text(reason or ""));
198 end
199
200 function room_mt:set_role(nick, role, reason)
201 self:admin_set(nick, "role", role, reason);
202 end
203
204 function room_mt:set_affiliation(nick, affiliation, reason)
205 self:admin_set(nick, "affiliation", affiliation, reason);
206 end
207
208 function room_mt:kick(nick, reason)
209 self:set_role(nick, "none", reason);
210 end
211
212 function room_mt:ban(nick, reason)
213 self:set_affiliation(nick, "outcast", reason);
214 end
215
216 function room_mt:event(name, arg)
217 self.stream:debug("Firing room event: %s", name);
218 return self.events.fire_event(name, arg);
219 end
220
221 function room_mt:hook(name, callback, priority)
222 return self.events.add_handler(name, callback, priority);
223 end
224 end)
225 package.preload['verse.component'] = (function (...)
226 local verse = require "verse";
227 local stream = verse.stream_mt;
228
229 local jid_split = require "util.jid".split;
230 local lxp = require "lxp";
231 local st = require "util.stanza";
232 local sha1 = require "util.sha1".sha1;
233
234 -- Shortcuts to save having to load util.stanza
235 verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
236 st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
237
238 local new_xmpp_stream = require "util.xmppstream".new;
239
240 local xmlns_stream = "http://etherx.jabber.org/streams";
241 local xmlns_component = "jabber:component:accept";
242
243 local stream_callbacks = {
244 stream_ns = xmlns_stream,
245 stream_tag = "stream",
246 default_ns = xmlns_component };
247
248 function stream_callbacks.streamopened(stream, attr)
249 stream.stream_id = attr.id;
250 if not stream:event("opened", attr) then
251 stream.notopen = nil;
252 end
253 return true;
254 end
255
256 function stream_callbacks.streamclosed(stream)
257 return stream:event("closed");
258 end
259
260 function stream_callbacks.handlestanza(stream, stanza)
261 if stanza.attr.xmlns == xmlns_stream then
262 return stream:event("stream-"..stanza.name, stanza);
263 elseif stanza.attr.xmlns or stanza.name == "handshake" then
264 return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza);
265 end
266
267 return stream:event("stanza", stanza);
268 end
269
270 function stream:reset()
271 if self.stream then
272 self.stream:reset();
273 else
274 self.stream = new_xmpp_stream(self, stream_callbacks);
275 end
276 self.notopen = true;
277 return true;
278 end
279
280 function stream:connect_component(jid, pass)
281 self.jid, self.password = jid, pass;
282 self.username, self.host, self.resource = jid_split(jid);
283
284 function self.data(conn, data)
285 local ok, err = self.stream:feed(data);
286 if ok then return; end
287 stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
288 stream:close("xml-not-well-formed");
289 end
290
291 self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
292
293 self.curr_id = 0;
294
295 self.tracked_iqs = {};
296 self:hook("stanza", function (stanza)
297 local id, type = stanza.attr.id, stanza.attr.type;
298 if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
299 self.tracked_iqs[id](stanza);
300 self.tracked_iqs[id] = nil;
301 return true;
302 end
303 end);
304
305 self:hook("stanza", function (stanza)
306 if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
307 if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
308 local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
309 if xmlns then
310 ret = self:event("iq/"..xmlns, stanza);
311 if not ret then
312 ret = self:event("iq", stanza);
313 end
314 end
315 if ret == nil then
316 self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
317 return true;
318 end
319 else
320 ret = self:event(stanza.name, stanza);
321 end
322 end
323 return ret;
324 end, -1);
325
326 self:hook("opened", function (attr)
327 print(self.jid, self.stream_id, attr.id);
328 local token = sha1(self.stream_id..pass, true);
329
330 self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token));
331 self:hook("stream/"..xmlns_component, function (stanza)
332 if stanza.name == "handshake" then
333 self:event("authentication-success");
334 end
335 end);
336 end);
337
338 local function stream_ready()
339 self:event("ready");
340 end
341 self:hook("authentication-success", stream_ready, -1);
342
343 -- Initialise connection
344 self:connect(self.connect_host or self.host, self.connect_port or 5347);
345 self:reopen();
346 end
347
348 function stream:reopen()
349 self:reset();
350 self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams',
351 xmlns = xmlns_component, version = "1.0" }):top_tag());
352 end
353
354 function stream:close(reason)
355 if not self.notopen then
356 self:send("</stream:stream>");
357 end
358 local on_disconnect = self.conn.disconnect();
359 self.conn:close();
360 on_disconnect(conn, reason);
361 end
362
363 function stream:send_iq(iq, callback)
364 local id = self:new_id();
365 self.tracked_iqs[id] = callback;
366 iq.attr.id = id;
367 self:send(iq);
368 end
369
370 function stream:new_id()
371 self.curr_id = self.curr_id + 1;
372 return tostring(self.curr_id);
373 end
374 end)
375
376 -- Use LuaRocks if available
377 pcall(require, "luarocks.require");
378
379 -- Load LuaSec if available
380 pcall(require, "ssl");
381
382 local server = require "net.server";
383 local events = require "util.events";
384 local logger = require "util.logger";
385
386 module("verse", package.seeall);
387 local verse = _M;
388 _M.server = server;
389
390 local stream = {};
391 stream.__index = stream;
392 stream_mt = stream;
393
394 verse.plugins = {};
395
396 local max_id = 0;
397
398 function verse.new(logger, base)
399 local t = setmetatable(base or {}, stream);
400 max_id = max_id + 1;
401 t.id = tostring(max_id);
402 t.logger = logger or verse.new_logger("stream"..t.id);
403 t.events = events.new();
404 t.plugins = {};
405 t.verse = verse;
406 return t;
407 end
408
409 verse.add_task = require "util.timer".add_task;
410
411 verse.logger = logger.init; -- COMPAT: Deprecated
412 verse.new_logger = logger.init;
413 verse.log = verse.logger("verse");
414
415 local function format(format, ...)
416 local n, arg, maxn = 0, { ... }, select('#', ...);
417 return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end));
418 end
419
420 function verse.set_log_handler(log_handler, levels)
421 levels = levels or { "debug", "info", "warn", "error" };
422 logger.reset();
423 local function _log_handler(name, level, message, ...)
424 return log_handler(name, level, format(message, ...));
425 end
426 if log_handler then
427 for i, level in ipairs(levels) do
428 logger.add_level_sink(level, _log_handler);
429 end
430 end
431 end
432
433 function _default_log_handler(name, level, message)
434 return io.stderr:write(name, "\t", level, "\t", message, "\n");
435 end
436 verse.set_log_handler(_default_log_handler, { "error" });
437
438 local function error_handler(err)
439 verse.log("error", "Error: %s", err);
440 verse.log("error", "Traceback: %s", debug.traceback());
441 end
442
443 function verse.set_error_handler(new_error_handler)
444 error_handler = new_error_handler;
445 end
446
447 function verse.loop()
448 return xpcall(server.loop, error_handler);
449 end
450
451 function verse.step()
452 return xpcall(server.step, error_handler);
453 end
454
455 function verse.quit()
456 return server.setquitting(true);
457 end
458
459 function stream:connect(connect_host, connect_port)
460 connect_host = connect_host or "localhost";
461 connect_port = tonumber(connect_port) or 5222;
462
463 -- Create and initiate connection
464 local conn = socket.tcp()
465 conn:settimeout(0);
466 local success, err = conn:connect(connect_host, connect_port);
467
468 if not success and err ~= "timeout" then
469 self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err);
470 return self:event("disconnected", { reason = err }) or false, err;
471 end
472
473 local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a");
474 if not conn then
475 self:warn("connection initialisation failed: %s", err);
476 return self:event("disconnected", { reason = err }) or false, err;
477 end
478
479 self.conn = conn;
480 self.send = function (stream, data)
481 self:event("outgoing", data);
482 data = tostring(data);
483 self:event("outgoing-raw", data);
484 return conn:write(data);
485 end;
486 return true;
487 end
488
489 function stream:close()
490 if not self.conn then
491 verse.log("error", "Attempt to close disconnected connection - possibly a bug");
492 return;
493 end
494 local on_disconnect = self.conn.disconnect();
495 self.conn:close();
496 on_disconnect(conn, reason);
497 end
498
499 -- Logging functions
500 function stream:debug(...)
501 return self.logger("debug", ...);
502 end
503
504 function stream:warn(...)
505 return self.logger("warn", ...);
506 end
507
508 function stream:error(...)
509 return self.logger("error", ...);
510 end
511
512 -- Event handling
513 function stream:event(name, ...)
514 self:debug("Firing event: "..tostring(name));
515 return self.events.fire_event(name, ...);
516 end
517
518 function stream:hook(name, ...)
519 return self.events.add_handler(name, ...);
520 end
521
522 function stream:unhook(name, handler)
523 return self.events.remove_handler(name, handler);
524 end
525
526 function verse.eventable(object)
527 object.events = events.new();
528 object.hook, object.unhook = stream.hook, stream.unhook;
529 local fire_event = object.events.fire_event;
530 function object:event(name, ...)
531 return fire_event(name, ...);
532 end
533 return object;
534 end
535
536 function stream:add_plugin(name)
537 if self.plugins[name] then return true; end
538 if require("verse.plugins."..name) then
539 local ok, err = verse.plugins[name](self);
540 if ok ~= false then
541 self:debug("Loaded %s plugin", name);
542 self.plugins[name] = true;
543 else
544 self:warn("Failed to load %s plugin: %s", name, err);
545 end
546 end
547 return self;
548 end
549
550 -- Listener factory
551 function new_listener(stream)
552 local conn_listener = {};
553
554 function conn_listener.onconnect(conn)
555 stream.connected = true;
556 stream:event("connected");
557 end
558
559 function conn_listener.onincoming(conn, data)
560 stream:event("incoming-raw", data);
561 end
562
563 function conn_listener.ondisconnect(conn, err)
564 stream.connected = false;
565 stream:event("disconnected", { reason = err });
566 end
567
568 function conn_listener.ondrain(conn)
569 stream:event("drained");
570 end
571
572 function conn_listener.onstatus(conn, new_status)
573 stream:event("status", new_status);
574 end
575
576 return conn_listener;
577 end
578
579 return verse;
580