Mercurial > prosody-modules
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 |