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 |
