mod_xxxx/verse.orig/verse.lua

581 lines
15 KiB
Lua

package.preload['verse.plugins.presence'] = (function (...)
function verse.plugins.presence(stream)
stream.last_presence = nil;
stream:hook("presence-out", function (presence)
if not presence.attr.to then
stream.last_presence = presence; -- Cache non-directed presence
end
end, 1);
function stream:resend_presence()
if last_presence then
stream:send(last_presence);
end
end
function stream:set_status(opts)
local p = verse.presence();
if type(opts) == "table" then
if opts.show then
p:tag("show"):text(opts.show):up();
end
if opts.prio then
p:tag("priority"):text(tostring(opts.prio)):up();
end
if opts.msg then
p:tag("status"):text(opts.msg):up();
end
end
-- TODO maybe use opts as prio if it's a int,
-- or as show or status if it's a string?
stream:send(p);
end
end
end)
package.preload['verse.plugins.groupchat'] = (function (...)
local events = require "util.events";
local room_mt = {};
room_mt.__index = room_mt;
local xmlns_delay = "urn:xmpp:delay";
local xmlns_muc = "http://jabber.org/protocol/muc";
function verse.plugins.groupchat(stream)
stream:add_plugin("presence")
stream.rooms = {};
stream:hook("stanza", function (stanza)
local room_jid = jid.bare(stanza.attr.from);
if not room_jid then return end
local room = stream.rooms[room_jid]
if not room and stanza.attr.to and room_jid then
room = stream.rooms[stanza.attr.to.." "..room_jid]
end
if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end
if room then
local nick = select(3, jid.split(stanza.attr.from));
local body = stanza:get_child_text("body");
local delay = stanza:get_child("delay", xmlns_delay);
local event = {
room_jid = room_jid;
room = room;
sender = room.occupants[nick];
nick = nick;
body = body;
stanza = stanza;
delay = (delay and delay.attr.stamp);
};
local ret = room:event(stanza.name, event);
return ret or (stanza.name == "message") or nil;
end
end, 500);
function stream:join_room(jid, nick, opts)
if not nick then
return false, "no nickname supplied"
end
opts = opts or {};
local room = setmetatable({
stream = stream, jid = jid, nick = nick,
subject = nil,
occupants = {},
opts = opts,
events = events.new()
}, room_mt);
if opts.source then
self.rooms[opts.source.." "..jid] = room;
else
self.rooms[jid] = room;
end
local occupants = room.occupants;
room:hook("presence", function (presence)
local nick = presence.nick or nick;
if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then
occupants[nick] = {
nick = nick;
jid = presence.stanza.attr.from;
presence = presence.stanza;
};
local x = presence.stanza:get_child("x", xmlns_muc .. "#user");
if x then
local x_item = x:get_child("item");
if x_item and x_item.attr then
occupants[nick].real_jid = x_item.attr.jid;
occupants[nick].affiliation = x_item.attr.affiliation;
occupants[nick].role = x_item.attr.role;
end
--TODO Check for status 100?
end
if nick == room.nick then
room.stream:event("groupchat/joined", room);
else
room:event("occupant-joined", occupants[nick]);
end
elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then
if nick == room.nick then
room.stream:event("groupchat/left", room);
if room.opts.source then
self.rooms[room.opts.source.." "..jid] = nil;
else
self.rooms[jid] = nil;
end
else
occupants[nick].presence = presence.stanza;
room:event("occupant-left", occupants[nick]);
occupants[nick] = nil;
end
end
end);
room:hook("message", function(event)
local subject = event.stanza:get_child_text("subject");
if not subject then return end
subject = #subject > 0 and subject or nil;
if subject ~= room.subject then
local old_subject = room.subject;
room.subject = subject;
return room:event("subject-changed", { from = old_subject, to = subject, by = event.sender, event = event });
end
end, 2000);
local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset();
self:event("pre-groupchat/joining", join_st);
room:send(join_st)
self:event("groupchat/joining", room);
return room;
end
stream:hook("presence-out", function(presence)
if not presence.attr.to then
for _, room in pairs(stream.rooms) do
room:send(presence);
end
presence.attr.to = nil;
end
end);
end
function room_mt:send(stanza)
if stanza.name == "message" and not stanza.attr.type then
stanza.attr.type = "groupchat";
end
if stanza.name == "presence" then
stanza.attr.to = self.jid .."/"..self.nick;
end
if stanza.attr.type == "groupchat" or not stanza.attr.to then
stanza.attr.to = self.jid;
end
if self.opts.source then
stanza.attr.from = self.opts.source
end
self.stream:send(stanza);
end
function room_mt:send_message(text)
self:send(verse.message():tag("body"):text(text));
end
function room_mt:set_subject(text)
self:send(verse.message():tag("subject"):text(text));
end
function room_mt:change_nick(new)
self.nick = new;
self:send(verse.presence());
end
function room_mt:leave(message)
self.stream:event("groupchat/leaving", self);
self:send(verse.presence({type="unavailable"}));
end
function room_mt:admin_set(nick, what, value, reason)
self:send(verse.iq({type="set"})
:query(xmlns_muc .. "#admin")
:tag("item", {nick = nick, [what] = value})
:tag("reason"):text(reason or ""));
end
function room_mt:set_role(nick, role, reason)
self:admin_set(nick, "role", role, reason);
end
function room_mt:set_affiliation(nick, affiliation, reason)
self:admin_set(nick, "affiliation", affiliation, reason);
end
function room_mt:kick(nick, reason)
self:set_role(nick, "none", reason);
end
function room_mt:ban(nick, reason)
self:set_affiliation(nick, "outcast", reason);
end
function room_mt:event(name, arg)
self.stream:debug("Firing room event: %s", name);
return self.events.fire_event(name, arg);
end
function room_mt:hook(name, callback, priority)
return self.events.add_handler(name, callback, priority);
end
end)
package.preload['verse.component'] = (function (...)
local verse = require "verse";
local stream = verse.stream_mt;
local jid_split = require "util.jid".split;
local lxp = require "lxp";
local st = require "util.stanza";
local sha1 = require "util.sha1".sha1;
-- Shortcuts to save having to load util.stanza
verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
local new_xmpp_stream = require "util.xmppstream".new;
local xmlns_stream = "http://etherx.jabber.org/streams";
local xmlns_component = "jabber:component:accept";
local stream_callbacks = {
stream_ns = xmlns_stream,
stream_tag = "stream",
default_ns = xmlns_component };
function stream_callbacks.streamopened(stream, attr)
stream.stream_id = attr.id;
if not stream:event("opened", attr) then
stream.notopen = nil;
end
return true;
end
function stream_callbacks.streamclosed(stream)
return stream:event("closed");
end
function stream_callbacks.handlestanza(stream, stanza)
if stanza.attr.xmlns == xmlns_stream then
return stream:event("stream-"..stanza.name, stanza);
elseif stanza.attr.xmlns or stanza.name == "handshake" then
return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza);
end
return stream:event("stanza", stanza);
end
function stream:reset()
if self.stream then
self.stream:reset();
else
self.stream = new_xmpp_stream(self, stream_callbacks);
end
self.notopen = true;
return true;
end
function stream:connect_component(jid, pass)
self.jid, self.password = jid, pass;
self.username, self.host, self.resource = jid_split(jid);
function self.data(conn, data)
local ok, err = self.stream:feed(data);
if ok then return; end
stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
stream:close("xml-not-well-formed");
end
self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
self.curr_id = 0;
self.tracked_iqs = {};
self:hook("stanza", function (stanza)
local id, type = stanza.attr.id, stanza.attr.type;
if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
self.tracked_iqs[id](stanza);
self.tracked_iqs[id] = nil;
return true;
end
end);
self:hook("stanza", function (stanza)
if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
if xmlns then
ret = self:event("iq/"..xmlns, stanza);
if not ret then
ret = self:event("iq", stanza);
end
end
if ret == nil then
self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
return true;
end
else
ret = self:event(stanza.name, stanza);
end
end
return ret;
end, -1);
self:hook("opened", function (attr)
print(self.jid, self.stream_id, attr.id);
local token = sha1(self.stream_id..pass, true);
self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token));
self:hook("stream/"..xmlns_component, function (stanza)
if stanza.name == "handshake" then
self:event("authentication-success");
end
end);
end);
local function stream_ready()
self:event("ready");
end
self:hook("authentication-success", stream_ready, -1);
-- Initialise connection
self:connect(self.connect_host or self.host, self.connect_port or 5347);
self:reopen();
end
function stream:reopen()
self:reset();
self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams',
xmlns = xmlns_component, version = "1.0" }):top_tag());
end
function stream:close(reason)
if not self.notopen then
self:send("</stream:stream>");
end
local on_disconnect = self.conn.disconnect();
self.conn:close();
on_disconnect(conn, reason);
end
function stream:send_iq(iq, callback)
local id = self:new_id();
self.tracked_iqs[id] = callback;
iq.attr.id = id;
self:send(iq);
end
function stream:new_id()
self.curr_id = self.curr_id + 1;
return tostring(self.curr_id);
end
end)
-- Use LuaRocks if available
pcall(require, "luarocks.require");
-- Load LuaSec if available
pcall(require, "ssl");
local server = require "net.server";
local events = require "util.events";
local logger = require "util.logger";
module("verse", package.seeall);
local verse = _M;
_M.server = server;
local stream = {};
stream.__index = stream;
stream_mt = stream;
verse.plugins = {};
local max_id = 0;
function verse.new(logger, base)
local t = setmetatable(base or {}, stream);
max_id = max_id + 1;
t.id = tostring(max_id);
t.logger = logger or verse.new_logger("stream"..t.id);
t.events = events.new();
t.plugins = {};
t.verse = verse;
return t;
end
verse.add_task = require "util.timer".add_task;
verse.logger = logger.init; -- COMPAT: Deprecated
verse.new_logger = logger.init;
verse.log = verse.logger("verse");
local function format(format, ...)
local n, arg, maxn = 0, { ... }, select('#', ...);
return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end));
end
function verse.set_log_handler(log_handler, levels)
levels = levels or { "debug", "info", "warn", "error" };
logger.reset();
local function _log_handler(name, level, message, ...)
return log_handler(name, level, format(message, ...));
end
if log_handler then
for i, level in ipairs(levels) do
logger.add_level_sink(level, _log_handler);
end
end
end
function _default_log_handler(name, level, message)
return io.stderr:write(name, "\t", level, "\t", message, "\n");
end
verse.set_log_handler(_default_log_handler, { "error" });
local function error_handler(err)
verse.log("error", "Error: %s", err);
verse.log("error", "Traceback: %s", debug.traceback());
end
function verse.set_error_handler(new_error_handler)
error_handler = new_error_handler;
end
function verse.loop()
return xpcall(server.loop, error_handler);
end
function verse.step()
return xpcall(server.step, error_handler);
end
function verse.quit()
return server.setquitting(true);
end
function stream:connect(connect_host, connect_port)
connect_host = connect_host or "localhost";
connect_port = tonumber(connect_port) or 5222;
-- Create and initiate connection
local conn = socket.tcp()
conn:settimeout(0);
local success, err = conn:connect(connect_host, connect_port);
if not success and err ~= "timeout" then
self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err);
return self:event("disconnected", { reason = err }) or false, err;
end
local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a");
if not conn then
self:warn("connection initialisation failed: %s", err);
return self:event("disconnected", { reason = err }) or false, err;
end
self.conn = conn;
self.send = function (stream, data)
self:event("outgoing", data);
data = tostring(data);
self:event("outgoing-raw", data);
return conn:write(data);
end;
return true;
end
function stream:close()
if not self.conn then
verse.log("error", "Attempt to close disconnected connection - possibly a bug");
return;
end
local on_disconnect = self.conn.disconnect();
self.conn:close();
on_disconnect(conn, reason);
end
-- Logging functions
function stream:debug(...)
return self.logger("debug", ...);
end
function stream:warn(...)
return self.logger("warn", ...);
end
function stream:error(...)
return self.logger("error", ...);
end
-- Event handling
function stream:event(name, ...)
self:debug("Firing event: "..tostring(name));
return self.events.fire_event(name, ...);
end
function stream:hook(name, ...)
return self.events.add_handler(name, ...);
end
function stream:unhook(name, handler)
return self.events.remove_handler(name, handler);
end
function verse.eventable(object)
object.events = events.new();
object.hook, object.unhook = stream.hook, stream.unhook;
local fire_event = object.events.fire_event;
function object:event(name, ...)
return fire_event(name, ...);
end
return object;
end
function stream:add_plugin(name)
if self.plugins[name] then return true; end
if require("verse.plugins."..name) then
local ok, err = verse.plugins[name](self);
if ok ~= false then
self:debug("Loaded %s plugin", name);
self.plugins[name] = true;
else
self:warn("Failed to load %s plugin: %s", name, err);
end
end
return self;
end
-- Listener factory
function new_listener(stream)
local conn_listener = {};
function conn_listener.onconnect(conn)
stream.connected = true;
stream:event("connected");
end
function conn_listener.onincoming(conn, data)
stream:event("incoming-raw", data);
end
function conn_listener.ondisconnect(conn, err)
stream.connected = false;
stream:event("disconnected", { reason = err });
end
function conn_listener.ondrain(conn)
stream:event("drained");
end
function conn_listener.onstatus(conn, new_status)
stream:event("status", new_status);
end
return conn_listener;
end
return verse;