changeset 0:0d4d8b432980 default tip master

Initial commit.
author Kooda <kooda@upyum.com>
date Fri, 27 Aug 2010 17:00:32 +0200
parents
children
files config.lua libs/utils.lua tiny-nix.lua verse.lua
diffstat 4 files changed, 3340 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/config.lua
@@ -0,0 +1,21 @@
+jid = "bot@upyum.com";
+password = "bot";
+nickname = "Nix-ng";
+debug = true;
+mode="normal" -- Ne sert à rien…
+
+-- Les admins ne sont rien pour l'instant.
+admins = {
+	"kooda@upyum.com",
+	"apteno@im.apteno.fr"
+};
+
+muc1 = {
+	jid = "test1@muc.upyum.com";
+	-- password = "blabla";
+};
+
+muc2 = {
+	jid = "test2@muc.upyum.com";
+	-- password = "blabla";
+};
new file mode 100644
--- /dev/null
+++ b/libs/utils.lua
@@ -0,0 +1,54 @@
+--
+--------------------------------------------------------------------------------
+--         FILE:  utils.lua
+--        USAGE:  ./utils.lua 
+--  DESCRIPTION:  A couple of functions
+--      OPTIONS:  ---
+-- REQUIREMENTS:  ---
+--         BUGS:  ---
+--        NOTES:  ---
+--       AUTHOR:  Adrien Ramos (M), <adrien.ramos@upyum.com>
+--      COMPANY:  
+--      VERSION:  1.0
+--      CREATED:  07/08/2010 23:40:24 CEST
+--     REVISION:  ---
+--------------------------------------------------------------------------------
+--
+
+local verse = require("verse");
+local st = require("util.stanza");
+
+-- Lit le fichier de configuration (par défaut config.lua) et stocke la configuration
+-- dans la table config
+function config_load(filename)
+	local filename = filename or "config.lua"
+	-- Config loading
+	local chunk, err = loadfile(filename);
+	if not chunk then
+		print("File or syntax error:", err);
+		return 1;
+	end
+
+	local config = {};
+	setfenv(chunk, setmetatable(config, {__index = _G}));
+	local ok, err = pcall(chunk);
+	if not ok then
+		print("Error while processing config:", err);
+		return 1;
+	end
+	setmetatable(config, nil)
+	return config;
+end
+
+function room_join(stream, room, nick)
+	if not nick or not room or not stream then
+		error("room_join needs the following arguments : stream room nick");
+	end
+	s = st.presence{from=stream.username.."@"..stream.host.."/"..stream.resource, to = room.jid.."/"..nick};
+	s:tag("x", {xmlns="http://jabber.org/protocol/muc"});
+	if room.password then
+		s:tag("password"):text(room.password);
+	end
+	print("XML OUT:", s);
+	stream:send(s);
+end
new file mode 100755
--- /dev/null
+++ b/tiny-nix.lua
@@ -0,0 +1,58 @@
+require("libs.utils");
+local verse = require("verse");
+require("verse.client");
+
+local config = config_load();
+room_map = {
+	[config.muc1.jid] = config.muc2.jid;
+	[config.muc2.jid] = config.muc1.jid;
+};
+local bot = verse.new();
+local occupants = {
+	[config.muc1.jid] = {};
+	[config.muc2.jid] = {};
+};
+
+function new_clone(table, room, nick, presence)
+	table[room][nick] = verse.new();
+	table[room][nick]:connect_client(config.jid.."/"..nick, config.password);
+	table[room][nick]:hook("ready", function()
+		table[room][nick]:send(presence);
+	end);
+end;
+
+function stanza_split(stanza)
+	return jid.bare(stanza.attr.from),
+	room_map[jid.bare(stanza.attr.from)],
+	select(3, jid.split(stanza.attr.from));
+end
+
+bot:hook("ready", function()
+	room_join(bot, config.muc1, config.nickname);
+	room_join(bot, config.muc2, config.nickname);
+end);
+
+bot:hook("stanza", function(stanza)
+	local room_from, room_to, nick = stanza_split(stanza);
+	if not room_from or not room_to or not nick or nick == config.nickname then return; end;
+	if occupants[room_from][nick] then return; end;
+	if stanza.name == "message" and stanza.attr.type ~= "groupchat" then return; end;
+
+	local reply = stanza; reply.attr.from = nil;
+	reply.attr.to = room_to;
+	if stanza.name == "presence" then 
+		reply.attr.to = room_to.."/"..nick;
+		if not occupants[room_to][nick] then new_clone(occupants, room_to, nick, reply); end;
+		if stanza.attr.type == "unavailable" then
+			occupants[room_to][nick]:send(reply);
+			occupants[room_to][nick]:close();
+			occupants[room_to][nick] = nil;
+			occupants[room_from][nick] = nil;
+			return;
+		end
+	end
+	if occupants[room_to][nick] then occupants[room_to][nick]:send(reply); end;
+end);
+
+bot:connect_client(config.jid, config.password);
+verse.loop();
new file mode 100644
--- /dev/null
+++ b/verse.lua
@@ -0,0 +1,3207 @@
+package.preload['util.encodings']=(function(...)
+local function e()
+error("Encoding function not implemented");
+end
+module"encodings"
+stringprep={};
+base64={encode=e,decode=e};
+return _M;
+end)
+package.preload['util.logger']=(function(...)
+local o=print
+local i,t=select,tostring;
+module"logger"
+local function e(o,...)
+local e,a=0,#arg;
+return(o:gsub("%%(.)",function(o)if o~="%"and e<=a then e=e+1;return t(arg[e]);end end));
+end
+local function n(a,...)
+local e,o=0,i('#',...);
+local i={...};
+return(a:gsub("%%(.)",function(a)if e<=o then e=e+1;return t(i[e]);end end));
+end
+function init(e)
+return function(e,t,...)
+o(e,n(t,...));
+end
+end
+return _M;
+end)
+package.preload['util.xstanza']=(function(...)
+local e=getmetatable(require"util.stanza".stanza());
+local i="urn:ietf:params:xml:ns:xmpp-stanzas";
+function e:get_error()
+local o,t,a;
+local e=self:get_child("error");
+if not e then
+return nil,nil;
+end
+o=e.attr.type;
+for e in e:children()do
+if e.attr.xmlns==i then
+if e.name=="text"then
+a=e:get_text();
+else
+t=e.name;
+end
+if t and a then
+break;
+end
+end
+end
+return o,t,a;
+end
+end)
+package.preload['util.stanza']=(function(...)
+local e=table.insert;
+local t=table.concat;
+local r=table.remove;
+local y=table.concat;
+local s=string.format;
+local w=string.match;
+local l=tostring;
+local d=setmetatable;
+local p=getmetatable;
+local i=pairs;
+local n=ipairs;
+local o=type;
+local t=next;
+local t=print;
+local t=unpack;
+local m=string.gsub;
+local t=string.char;
+local u=string.find;
+local t=os;
+local c=not t.getenv("WINDIR");
+local h,a;
+if c then
+local t,e=pcall(require,"util.termcolours");
+if t then
+h,a=e.getstyle,e.getstring;
+else
+c=nil;
+end
+end
+local f="urn:ietf:params:xml:ns:xmpp-stanzas";
+module"stanza"
+stanza_mt={__type="stanza"};
+stanza_mt.__index=stanza_mt;
+function stanza(t,e)
+local e={name=t,attr=e or{},tags={},last_add={}};
+return d(e,stanza_mt);
+end
+function stanza_mt:query(e)
+return self:tag("query",{xmlns=e});
+end
+function stanza_mt:body(e,t)
+return self:tag("body",t):text(e);
+end
+function stanza_mt:tag(a,t)
+local t=stanza(a,t);
+(self.last_add[#self.last_add]or self):add_direct_child(t);
+e(self.last_add,t);
+return self;
+end
+function stanza_mt:text(e)
+(self.last_add[#self.last_add]or self):add_direct_child(e);
+return self;
+end
+function stanza_mt:up()
+r(self.last_add);
+return self;
+end
+function stanza_mt:reset()
+local e=self.last_add;
+for t=1,#e do
+e[t]=nil;
+end
+return self;
+end
+function stanza_mt:add_direct_child(t)
+if o(t)=="table"then
+e(self.tags,t);
+end
+e(self,t);
+end
+function stanza_mt:add_child(e)
+(self.last_add[#self.last_add]or self):add_direct_child(e);
+return self;
+end
+function stanza_mt:get_child(a,t)
+for o,e in n(self.tags)do
+if(not a or e.name==a)
+and((not t and self.attr.xmlns==e.attr.xmlns)
+or e.attr.xmlns==t)then
+return e;
+end
+end
+end
+function stanza_mt:child_with_name(t)
+for a,e in n(self.tags)do
+if e.name==t then return e;end
+end
+end
+function stanza_mt:child_with_ns(t)
+for a,e in n(self.tags)do
+if e.attr.xmlns==t then return e;end
+end
+end
+function stanza_mt:children()
+local e=0;
+return function(t)
+e=e+1
+local e=t[e]
+if e then return e;end
+end,self,e;
+end
+function stanza_mt:childtags()
+local e=0;
+return function(t)
+e=e+1
+local e=self.tags[e]
+if e then return e;end
+end,self.tags[1],e;
+end
+local r
+do
+local e={["'"]="&apos;",["\""]="&quot;",["<"]="&lt;",[">"]="&gt;",["&"]="&amp;"};
+function r(t)return(m(t,"['&<>\"]",e));end
+_M.xml_escape=r;
+end
+local function m(o,t,h,a,r)
+local n=0;
+local s=o.name
+e(t,"<"..s);
+for o,i in i(o.attr)do
+if u(o,"\1",1,true)then
+local o,s=w(o,"^([^\1]*)\1?(.*)$");
+n=n+1;
+e(t," xmlns:ns"..n.."='"..a(o).."' ".."ns"..n..":"..s.."='"..a(i).."'");
+elseif not(o=="xmlns"and i==r)then
+e(t," "..o.."='"..a(i).."'");
+end
+end
+local i=#o;
+if i==0 then
+e(t,"/>");
+else
+e(t,">");
+for i=1,i do
+local i=o[i];
+if i.name then
+h(i,t,h,a,o.attr.xmlns);
+else
+e(t,a(i));
+end
+end
+e(t,"</"..s..">");
+end
+end
+function stanza_mt.__tostring(t)
+local e={};
+m(t,e,m,r,nil);
+return y(e);
+end
+function stanza_mt.top_tag(t)
+local e="";
+if t.attr then
+for t,a in i(t.attr)do if o(t)=="string"then e=e..s(" %s='%s'",t,r(l(a)));end end
+end
+return s("<%s%s>",t.name,e);
+end
+function stanza_mt.get_text(e)
+if#e.tags==0 then
+return y(e);
+end
+end
+function stanza_mt.get_error(e)
+local o,a,t;
+local e=e:get_child("error");
+if not e then
+return nil,nil,nil;
+end
+o=e.attr.type;
+for e in e:children()do
+if e.attr.xmlns==f then
+if not t and e.name=="text"then
+t=e:get_text();
+elseif not a then
+a=e.name;
+end
+if a and t then
+break;
+end
+end
+end
+return o,a or"undefined-condition",t or"";
+end
+function stanza_mt.__add(e,t)
+return e:add_direct_child(t);
+end
+do
+local e=0;
+function new_id()
+e=e+1;
+return"lx"..e;
+end
+end
+function preserialize(a)
+local t={name=a.name,attr=a.attr};
+for i,a in n(a)do
+if o(a)=="table"then
+e(t,preserialize(a));
+else
+e(t,a);
+end
+end
+return t;
+end
+function deserialize(t)
+if t then
+local a=t.attr;
+for e=1,#a do a[e]=nil;end
+local s={};
+for e in i(a)do
+if u(e,"|",1,true)and not u(e,"\1",1,true)then
+local o,t=w(e,"^([^|]+)|(.+)$");
+s[o.."\1"..t]=a[e];
+a[e]=nil;
+end
+end
+for e,t in i(s)do
+a[e]=t;
+end
+d(t,stanza_mt);
+for t,e in n(t)do
+if o(e)=="table"then
+deserialize(e);
+end
+end
+if not t.tags then
+local a={};
+for n,i in n(t)do
+if o(i)=="table"then
+e(a,i);
+end
+end
+t.tags=a;
+if not t.last_add then
+t.last_add={};
+end
+end
+end
+return t;
+end
+function clone(n)
+local t={};
+local function a(e)
+if o(e)~="table"then
+return e;
+elseif t[e]then
+return t[e];
+end
+local o={};
+t[e]=o;
+for t,e in i(e)do
+o[a(t)]=a(e);
+end
+return d(o,p(e));
+end
+return a(n)
+end
+function message(t,e)
+if not e then
+return stanza("message",t);
+else
+return stanza("message",t):tag("body"):text(e);
+end
+end
+function iq(e)
+if e and not e.id then e.id=new_id();end
+return stanza("iq",e or{id=new_id()});
+end
+function reply(e)
+return stanza(e.name,e.attr and{to=e.attr.from,from=e.attr.to,id=e.attr.id,type=((e.name=="iq"and"result")or e.attr.type)});
+end
+do
+local a={xmlns=f};
+function error_reply(e,o,i,t)
+local e=reply(e);
+e.attr.type="error";
+e:tag("error",{type=o})
+:tag(i,a):up();
+if(t)then e:tag("text",a):text(t):up();end
+return e;
+end
+end
+function presence(e)
+return stanza("presence",e);
+end
+if c then
+local d=h("yellow");
+local u=h("red");
+local t=h("red");
+local e=h("magenta");
+local h=" "..a(d,"%s")..a(e,"=")..a(u,"'%s'");
+local d=a(e,"<")..a(t,"%s").."%s"..a(e,">");
+local u=d.."%s"..a(e,"</")..a(t,"%s")..a(e,">");
+function stanza_mt.pretty_print(t)
+local e="";
+for a,t in n(t)do
+if o(t)=="string"then
+e=e..r(t);
+else
+e=e..t:pretty_print();
+end
+end
+local a="";
+if t.attr then
+for e,t in i(t.attr)do if o(e)=="string"then a=a..s(h,e,l(t));end end
+end
+return s(u,t.name,a,e,t.name);
+end
+function stanza_mt.pretty_top_tag(t)
+local e="";
+if t.attr then
+for t,a in i(t.attr)do if o(t)=="string"then e=e..s(h,t,l(a));end end
+end
+return s(d,t.name,e);
+end
+else
+stanza_mt.pretty_print=stanza_mt.__tostring;
+stanza_mt.pretty_top_tag=stanza_mt.top_tag;
+end
+return _M;
+end)
+package.preload['util.timer']=(function(...)
+local d=require"net.server".addtimer;
+local i=require"net.server".event;
+local l=require"net.server".event_base;
+local n=os.time;
+local s=table.insert;
+local e=table.remove;
+local e,h=ipairs,pairs;
+local r=type;
+local o={};
+local a={};
+module"timer"
+local t;
+if not i then
+function t(e,o)
+local t=n();
+e=e+t;
+if e>=t then
+s(a,{e,o});
+else
+o();
+end
+end
+d(function()
+local e=n();
+if#a>0 then
+for t,e in h(a)do
+s(o,e);
+end
+a={};
+end
+for n,a in h(o)do
+local i,a=a[1],a[2];
+if i<=e then
+o[n]=nil;
+local e=a(e);
+if r(e)=="number"then t(e,a);end
+end
+end
+end);
+else
+local o=(i.core and i.core.LEAVE)or-1;
+function t(a,t)
+local e;
+e=l:addevent(nil,0,function()
+local t=t();
+if t then
+return 0,t;
+elseif e then
+return o;
+end
+end
+,a);
+end
+end
+add_task=t;
+return _M;
+end)
+package.preload['util.termcolours']=(function(...)
+local a,o=table.concat,table.insert;
+local e,i=string.char,string.format;
+local s=ipairs;
+module"termcolours"
+local h={
+reset=0;bright=1,dim=2,underscore=4,blink=5,reverse=7,hidden=8;
+black=30;red=31;green=32;yellow=33;blue=34;magenta=35;cyan=36;white=37;
+["black background"]=40;["red background"]=41;["green background"]=42;["yellow background"]=43;["blue background"]=44;["magenta background"]=45;["cyan background"]=46;["white background"]=47;
+bold=1,dark=2,underline=4,underlined=4,normal=0;
+}
+local n=e(27).."[%sm%s"..e(27).."[0m";
+function getstring(e,t)
+if e then
+return i(n,e,t);
+else
+return t;
+end
+end
+function getstyle(...)
+local e,t={...},{};
+for a,e in s(e)do
+e=h[e];
+if e then
+o(t,e);
+end
+end
+return a(t,";");
+end
+return _M;
+end)
+package.preload['util.uuid']=(function(...)
+local e=math.random;
+local n=tostring;
+local e=os.time;
+local i=os.clock;
+local a=require"util.hashes".sha1;
+module"uuid"
+local t=0;
+local function o()
+local e=e();
+if t>=e then e=t+1;end
+t=e;
+return e;
+end
+local function t(e)
+return a(e..i()..n({}),true);
+end
+local e=t(o());
+local function a(a)
+e=t(e..a);
+end
+local function t(t)
+if#e<t then a(o());end
+local a=e:sub(0,t);
+e=e:sub(t+1);
+return a;
+end
+local function e()
+return("%x"):format(t(1):byte()%4+8);
+end
+function generate()
+return t(8).."-"..t(4).."-4"..t(3).."-"..(e())..t(3).."-"..t(12);
+end
+seed=a;
+return _M;
+end)
+package.preload['net.server']=(function(...)
+local r=function(e)
+return _G[e]
+end
+local ee=function(e)
+for t,a in pairs(e)do
+e[t]=nil
+end
+end
+local O,e=require("util.logger").init("socket"),table.concat;
+local i=function(...)return O("debug",e{...});end
+local se=function(...)return O("warn",e{...});end
+local e=collectgarbage
+local ne=1
+local R=r"type"
+local q=r"pairs"
+local oe=r"ipairs"
+local s=r"tostring"
+local e=r"collectgarbage"
+local a=r"os"
+local o=r"table"
+local t=r"string"
+local e=r"coroutine"
+local Y=a.time
+local H=a.difftime
+local ae=o.concat
+local a=o.remove
+local te=t.len
+local ue=t.sub
+local ce=e.wrap
+local me=e.yield
+local g=r"ssl"
+local L=r"socket"or require"socket"
+local ie=(g and g.wrap)
+local de=L.bind
+local le=L.sleep
+local fe=L.select
+local e=(g and g.newcontext)
+local M
+local G
+local B
+local P
+local V
+local Q
+local K
+local X
+local J
+local Z
+local C
+local l
+local he
+local e
+local N
+local re
+local v
+local h
+local S
+local d
+local n
+local _
+local p
+local f
+local c
+local a
+local o
+local y
+local I
+local U
+local A
+local T
+local F
+local u
+local E
+local z
+local j
+local x
+local k
+local D
+local W
+local b
+v={}
+h={}
+d={}
+S={}
+n={}
+p={}
+f={}
+_={}
+a=0
+o=0
+y=0
+I=0
+U=0
+A=1
+T=0
+E=51e3*1024
+z=25e3*1024
+j=12e5
+x=6e4
+k=6*60*60
+D=false
+b=1e3
+_maxsslhandshake=30
+J=function(u,t,w,c,y,f,m)
+m=m or b
+local r=0
+local p,e=u.onconnect or u.onincoming,u.ondisconnect
+local v=t.accept
+local e={}
+e.shutdown=function()end
+e.ssl=function()
+return f~=nil
+end
+e.sslctx=function()
+return f
+end
+e.remove=function()
+r=r-1
+end
+e.close=function()
+for a,e in q(n)do
+if e.serverport==c then
+e.disconnect(e,"server closed")
+e:close(true)
+end
+end
+t:close()
+o=l(d,t,o)
+a=l(h,t,a)
+n[t]=nil
+e=nil
+t=nil
+i"server.lua: closed server handler and removed sockets from list"
+end
+e.ip=function()
+return w
+end
+e.serverport=function()
+return c
+end
+e.socket=function()
+return t
+end
+e.readbuffer=function()
+if r>m then
+i("server.lua: refused new client connection: server full")
+return false
+end
+local t,a=v(t)
+if t then
+local a,o=t:getpeername()
+t:settimeout(0)
+local e,n,t=N(e,u,t,a,c,o,y,f)
+if t then
+return false
+end
+r=r+1
+i("server.lua: accepted new client connection from ",s(a),":",s(o)," to ",s(c))
+return p(e)
+elseif a then
+i("server.lua: error with new client connection: ",s(a))
+return false
+end
+end
+return e
+end
+N=function(V,e,t,W,B,F,T,q)
+t:settimeout(0)
+local w
+local x
+local k
+local j
+local C=e.onincoming
+local R=e.onstatus
+local v=e.ondisconnect
+local L=e.ondrain
+local y={}
+local r=0
+local Y
+local H
+local O
+local m=0
+local b=false
+local A=false
+local S,N=0,0
+local E=E
+local z=z
+local e=y
+e.dispatch=function()
+return C
+end
+e.disconnect=function()
+return v
+end
+e.setlistener=function(a,t)
+C=t.onincoming
+v=t.ondisconnect
+R=t.onstatus
+L=t.ondrain
+end
+e.getstats=function()
+return N,S
+end
+e.ssl=function()
+return j
+end
+e.sslctx=function()
+return q
+end
+e.send=function(n,i,o,a)
+return w(t,i,o,a)
+end
+e.receive=function(o,a)
+return x(t,o,a)
+end
+e.shutdown=function(a)
+return k(t,a)
+end
+e.setoption=function(i,o,a)
+if t.setoption then
+return t:setoption(o,a);
+end
+return false,"setoption not implemented";
+end
+e.close=function(u,s)
+if not e then return true;end
+a=l(h,t,a)
+p[e]=nil
+if r~=0 then
+if not(s or H)then
+e.sendbuffer()
+if r~=0 then
+if e then
+e.write=nil
+end
+Y=true
+return false
+end
+else
+w(t,ae(y,"",1,r),1,m)
+end
+end
+if t then
+c=k and k(t)
+t:close()
+o=l(d,t,o)
+n[t]=nil
+t=nil
+else
+i"server.lua: socket already closed"
+end
+if e then
+f[e]=nil
+_[e]=nil
+e=nil
+end
+if V then
+V.remove()
+end
+i"server.lua: closed client handler and removed socket from list"
+return true
+end
+e.ip=function()
+return W
+end
+e.serverport=function()
+return B
+end
+e.clientport=function()
+return F
+end
+local _=function(i,a)
+m=m+te(a)
+if m>E then
+_[e]="send buffer exceeded"
+e.write=P
+return false
+elseif t and not d[t]then
+o=addsocket(d,t,o)
+end
+r=r+1
+y[r]=a
+if e then
+f[e]=f[e]or u
+end
+return true
+end
+e.write=_
+e.bufferqueue=function(t)
+return y
+end
+e.socket=function(a)
+return t
+end
+e.set_mode=function(a,t)
+T=t or T
+return T
+end
+e.set_send=function(a,t)
+w=t or w
+return w
+end
+e.bufferlen=function(o,a,t)
+E=t or E
+z=a or z
+return m,z,E
+end
+e.lock_read=function(i,o)
+if o==true then
+local o=a
+a=l(h,t,a)
+p[e]=nil
+if a~=o then
+b=true
+end
+elseif o==false then
+if b then
+b=false
+a=addsocket(h,t,a)
+p[e]=u
+end
+end
+return b
+end
+e.pause=function(t)
+return t:lock_read(true);
+end
+e.resume=function(t)
+return t:lock_read(false);
+end
+e.lock=function(i,a)
+e.lock_read(a)
+if a==true then
+e.write=P
+local a=o
+o=l(d,t,o)
+f[e]=nil
+if o~=a then
+A=true
+end
+elseif a==false then
+e.write=_
+if A then
+A=false
+_("")
+end
+end
+return b,A
+end
+local z=function()
+local o,t,a=x(t,T)
+if not t or(t=="wantread"or t=="timeout")then
+local o=o or a or""
+local a=te(o)
+if a>z then
+v(e,"receive buffer exceeded")
+e:close(true)
+return false
+end
+local a=a*ne
+N=N+a
+U=U+a
+p[e]=u
+return C(e,o,t)
+else
+i("server.lua: client ",s(W),":",s(F)," read error: ",s(t))
+H=true
+v(e,t)
+c=e and e:close()
+return false
+end
+end
+local f=function()
+local b,a,h,n,p;
+local p;
+if t then
+n=ae(y,"",1,r)
+b,a,h=w(t,n,1,m)
+p=(b or h or 0)*ne
+S=S+p
+I=I+p
+c=D and ee(y)
+else
+b,a,p=false,"closed",0;
+end
+if b then
+r=0
+m=0
+o=l(d,t,o)
+f[e]=nil
+if L then
+L(e)
+end
+c=O and e:starttls(nil)
+c=Y and e:close()
+return true
+elseif h and(a=="timeout"or a=="wantwrite")then
+n=ue(n,h+1,m)
+y[1]=n
+r=1
+m=m-h
+f[e]=u
+return true
+else
+i("server.lua: client ",s(W),":",s(F)," write error: ",s(a))
+H=true
+v(e,a)
+c=e and e:close()
+return false
+end
+end
+local u;
+function e.set_sslctx(n,t)
+j=true
+q=t;
+local m
+local r
+u=ce(function(t)
+local n
+for u=1,_maxsslhandshake do
+o=(m and l(d,t,o))or o
+a=(r and l(h,t,a))or a
+r,m=nil,nil
+c,n=t:dohandshake()
+if not n then
+i("server.lua: ssl handshake done")
+e.readbuffer=z
+e.sendbuffer=f
+c=R and R(e,"ssl-handshake-complete")
+a=addsocket(h,t,a)
+return true
+else
+i("server.lua: error during ssl handshake: ",s(n))
+if n=="wantwrite"and not m then
+o=addsocket(d,t,o)
+m=true
+elseif n=="wantread"and not r then
+a=addsocket(h,t,a)
+r=true
+else
+break;
+end
+me()
+end
+end
+v(e,"ssl handshake failed")
+c=e and e:close(true)
+return false
+end
+)
+end
+if g then
+if q then
+e:set_sslctx(q);
+i("server.lua: ","starting ssl handshake")
+local a
+t,a=ie(t,q)
+if a then
+i("server.lua: ssl error: ",s(a))
+return nil,nil,a
+end
+t:settimeout(0)
+e.readbuffer=u
+e.sendbuffer=u
+u(t)
+if not t then
+return nil,nil,"ssl handshake failed";
+end
+else
+local c;
+e.starttls=function(f,m)
+if m then
+c=m;
+e:set_sslctx(c);
+end
+if r>0 then
+i"server.lua: we need to do tls, but delaying until send buffer empty"
+O=true
+return
+end
+i("server.lua: attempting to start tls on "..s(t))
+local m,r=t
+t,r=ie(t,c)
+if r then
+i("server.lua: error while starting tls on client: ",s(r))
+return nil,r
+end
+t:settimeout(0)
+w=t.send
+x=t.receive
+k=M
+n[t]=e
+a=addsocket(h,t,a)
+a=l(h,m,a)
+o=l(d,m,o)
+n[m]=nil
+e.starttls=nil
+O=nil
+j=true
+e.readbuffer=u
+e.sendbuffer=u
+u(t)
+end
+e.readbuffer=z
+e.sendbuffer=f
+end
+else
+e.readbuffer=z
+e.sendbuffer=f
+end
+w=t.send
+x=t.receive
+k=(j and M)or t.shutdown
+n[t]=e
+a=addsocket(h,t,a)
+return e,t
+end
+M=function()
+end
+P=function()
+return false
+end
+addsocket=function(t,a,e)
+if not t[a]then
+e=e+1
+t[e]=a
+t[a]=e
+end
+return e;
+end
+l=function(e,a,t)
+local i=e[a]
+if i then
+e[a]=nil
+local o=e[t]
+e[t]=nil
+if o~=a then
+e[o]=i
+e[i]=o
+end
+return t-1
+end
+return t
+end
+C=function(e)
+o=l(d,e,o)
+a=l(h,e,a)
+n[e]=nil
+e:close()
+end
+local function l(e,t,o)
+local a;
+local i=t.sendbuffer;
+function t.sendbuffer()
+i();
+if a and t.bufferlen()<o then
+e:lock_read(false);
+a=nil;
+end
+end
+local i=e.readbuffer;
+function e.readbuffer()
+i();
+if not a and t.bufferlen()>=o then
+a=true;
+e:lock_read(true);
+end
+end
+end
+K=function(o,e,d,l,r)
+local t
+if R(d)~="table"then
+t="invalid listener table"
+end
+if R(e)~="number"or not(e>=0 and e<=65535)then
+t="invalid port"
+elseif v[e]then
+t="listeners on port '"..e.."' already exist"
+elseif r and not g then
+t="luasec not found"
+end
+if t then
+se("server.lua, port ",e,": ",t)
+return nil,t
+end
+o=o or"*"
+local t,s=de(o,e)
+if s then
+se("server.lua, port ",e,": ",s)
+return nil,s
+end
+local s,d=J(d,t,o,e,l,r,b)
+if not s then
+t:close()
+return nil,d
+end
+t:settimeout(0)
+a=addsocket(h,t,a)
+v[e]=s
+n[t]=s
+i("server.lua: new "..(r and"ssl "or"").."server listener on '",o,":",e,"'")
+return s
+end
+X=function(e)
+return v[e];
+end
+he=function(e)
+local t=v[e]
+if not t then
+return nil,"no server found on port '"..s(e).."'"
+end
+t:close()
+v[e]=nil
+return true
+end
+Q=function()
+for e,t in q(n)do
+t:close()
+n[e]=nil
+end
+a=0
+o=0
+y=0
+v={}
+h={}
+d={}
+S={}
+n={}
+end
+Z=function()
+return A,T,E,z,j,x,k,D,b,_maxsslhandshake
+end
+re=function(e)
+if R(e)~="table"then
+return nil,"invalid settings table"
+end
+A=tonumber(e.timeout)or A
+T=tonumber(e.sleeptime)or T
+E=tonumber(e.maxsendlen)or E
+z=tonumber(e.maxreadlen)or z
+j=tonumber(e.checkinterval)or j
+x=tonumber(e.sendtimeout)or x
+k=tonumber(e.readtimeout)or k
+D=e.cleanqueue
+b=e._maxclientsperserver or b
+_maxsslhandshake=e._maxsslhandshake or _maxsslhandshake
+return true
+end
+V=function(e)
+if R(e)~="function"then
+return nil,"invalid listener function"
+end
+y=y+1
+S[y]=e
+return true
+end
+B=function()
+return U,I,a,o,y
+end
+local e=true;
+setquitting=function(t)
+e=not t;
+return;
+end
+G=function()
+while e do
+local a,e,t=fe(h,d,A)
+for e,t in oe(e)do
+local e=n[t]
+if e then
+e.sendbuffer()
+else
+C(t)
+i"server.lua: found no handler and closed socket (writelist)"
+end
+end
+for t,e in oe(a)do
+local t=n[e]
+if t then
+t.readbuffer()
+else
+C(e)
+i"server.lua: found no handler and closed socket (readlist)"
+end
+end
+for e,t in q(_)do
+e.disconnect()(e,t)
+e:close(true)
+end
+ee(_)
+u=Y()
+if H(u-W)>=1 then
+for e=1,y do
+S[e](u)
+end
+W=u
+end
+le(T)
+end
+return"quitting"
+end
+local function h()
+return"select";
+end
+local i=function(t,i,s,a,e,h)
+local e=N(nil,a,t,i,s,"clientport",e,h)
+n[t]=e
+o=addsocket(d,t,o)
+if a.onconnect then
+local t=e.sendbuffer;
+e.sendbuffer=function()
+a.onconnect(e);
+e.sendbuffer=t;
+if#e:bufferqueue()>0 then
+return t();
+end
+end
+end
+return e,t
+end
+local a=function(o,a,n,s,h)
+local t,e=L.tcp()
+if e then
+return nil,e
+end
+t:settimeout(0)
+c,e=t:connect(o,a)
+if e then
+local e=i(t,o,a,n)
+else
+N(nil,n,t,o,a,"clientport",s,h)
+end
+end
+r"setmetatable"(n,{__mode="k"})
+r"setmetatable"(p,{__mode="k"})
+r"setmetatable"(f,{__mode="k"})
+W=Y()
+F=Y()
+V(function()
+local e=H(u-F)
+if e>j then
+F=u
+for e,t in q(f)do
+if H(u-t)>x then
+e.disconnect()(e,"send timeout")
+e:close(true)
+end
+end
+for e,t in q(p)do
+if H(u-t)>k then
+e.disconnect()(e,"read timeout")
+e:close()
+end
+end
+end
+end
+)
+local function t(e)
+local t=O;
+if e then
+O=e;
+end
+return t;
+end
+return{
+addclient=a,
+wrapclient=i,
+loop=G,
+link=l,
+stats=B,
+closeall=Q,
+addtimer=V,
+addserver=K,
+getserver=X,
+setlogger=t,
+getsettings=Z,
+setquitting=setquitting,
+removeserver=he,
+get_backend=h,
+changesettings=re,
+}
+end)
+package.preload['core.xmlhandlers']=(function(...)
+require"util.stanza"
+local y=stanza;
+local u=tostring;
+local w=table.insert;
+local l=table.concat;
+local r=require"util.logger".init("xmlhandlers");
+local s=error;
+module"xmlhandlers"
+local f={
+["http://www.w3.org/XML/1998/namespace"]="xml";
+};
+local i="http://etherx.jabber.org/streams";
+local t="\1";
+local h="^([^"..t.."]*)"..t.."?(.*)$";
+function init_xmlhandlers(a,e)
+local o={};
+local n={};
+local r=a.log or r;
+local r=e.streamopened;
+local d=e.streamclosed;
+local s=e.error or function(t,e)s("XML stream error: "..u(e));end;
+local m=e.handlestanza;
+local i=e.stream_ns or i;
+local c=i..t..(e.stream_tag or"stream");
+local p=i..t..(e.error_tag or"error");
+local u=e.default_ns;
+local e;
+function n:StartElement(n,t)
+if e and#o>0 then
+e:text(l(o));
+o={};
+end
+local o,i=n:match(h);
+if i==""then
+o,i="",o;
+end
+if o~=u then
+t.xmlns=o;
+end
+for e=1,#t do
+local a=t[e];
+t[e]=nil;
+local e,o=a:match(h);
+if o~=""then
+e=f[e];
+if e then
+t[e..":"..o]=t[a];
+t[a]=nil;
+end
+end
+end
+if not e then
+if a.notopen then
+if n==c then
+if r then
+r(a,t);
+end
+else
+s(a,"no-stream");
+end
+return;
+end
+if o=="jabber:client"and i~="iq"and i~="presence"and i~="message"then
+s(a,"invalid-top-level-element");
+end
+e=y.stanza(i,t);
+else
+t.xmlns=nil;
+if o~=u then
+t.xmlns=o;
+end
+e:tag(i,t);
+end
+end
+function n:CharacterData(t)
+if e then
+w(o,t);
+end
+end
+function n:EndElement(t)
+if e then
+if#o>0 then
+e:text(l(o));
+o={};
+end
+if#e.last_add==0 then
+if t~=p then
+m(a,e);
+else
+s(a,"stream-error",e);
+end
+e=nil;
+else
+e:up();
+end
+else
+if t==c then
+if d then
+d(a);
+end
+else
+local t,e=t:match(h);
+if e==""then
+t,e="",t;
+end
+s(a,"parse-error","unexpected-element-close",e);
+end
+e,o=nil,{};
+end
+end
+return n;
+end
+return init_xmlhandlers;
+end)
+package.preload['util.jid']=(function(...)
+local t=string.match;
+local h=require"util.encodings".stringprep.nodeprep;
+local s=require"util.encodings".stringprep.nameprep;
+local n=require"util.encodings".stringprep.resourceprep;
+module"jid"
+local function a(e)
+if not e then return;end
+local o,a=t(e,"^([^@]+)@()");
+local a,i=t(e,"^([^@/]+)()",a)
+if o and not a then return nil,nil,nil;end
+local t=t(e,"^/(.+)$",i);
+if(not a)or((not t)and#e>=i)then return nil,nil,nil;end
+return o,a,t;
+end
+split=a;
+function bare(e)
+local t,e=a(e);
+if t and e then
+return t.."@"..e;
+end
+return e;
+end
+local function o(e)
+local t,e,a=a(e);
+if e then
+e=s(e);
+if not e then return;end
+if t then
+t=h(t);
+if not t then return;end
+end
+if a then
+a=n(a);
+if not a then return;end
+end
+return t,e,a;
+end
+end
+prepped_split=o;
+function prep(e)
+local t,e,a=o(e);
+if e then
+if t then
+e=t.."@"..e;
+end
+if a then
+e=e.."/"..a;
+end
+end
+return e;
+end
+function join(t,e,a)
+if t and e and a then
+return t.."@"..e.."/"..a;
+elseif t and e then
+return t.."@"..e;
+elseif e and a then
+return e.."/"..a;
+elseif e then
+return e;
+end
+return nil;
+end
+function compare(t,e)
+local n,i,o=a(t);
+local t,a,e=a(e);
+if((t~=nil and t==n)or t==nil)and
+((a~=nil and a==i)or a==nil)and
+((e~=nil and e==o)or e==nil)then
+return true
+end
+return false
+end
+return _M;
+end)
+package.preload['util.events']=(function(...)
+local r=ipairs;
+local i=pairs;
+local s=table.insert;
+local h=table.sort;
+local d=select;
+module"events"
+function new()
+local o={};
+local t={};
+local a={};
+local function n(o)
+local a=a[o];
+local e=t[o];
+if e then
+for t=#e,1,-1 do e[t]=nil;end
+else e={};t[o]=e;end
+for t in i(a)do
+s(e,t);
+end
+h(e,function(t,e)return a[t]>a[e];end);
+end;
+local function s(t,o,i)
+local e=a[t];
+if e then
+e[o]=i or 0;
+else
+e={[o]=i or 0};
+a[t]=e;
+end
+n(t);
+end;
+local function h(t,o)
+local e=a[t];
+if e then
+e[o]=nil;
+n(t);
+end
+end;
+local function e(e)
+for e,t in i(e)do
+s(e,t);
+end
+end;
+local function e(e)
+for t,e in i(e)do
+h(t,e);
+end
+end;
+local function n(a)
+local e=t[a];
+if not e then e={};t[a]=e;end
+local e=function(...)
+for t=1,#e do
+local e=e[t](...);
+if e~=nil then return e;end
+end
+end;
+o[a]=e;
+return e;
+end;
+local function i(e)
+return o[e]or n(e);
+end;
+local function n(e,...)
+local e=t[e];
+if e then
+for t=1,#e do
+local e=e[t](...);
+if e~=nil then return e;end
+end
+end
+end;
+local function l(e,...)
+local a=i(e);
+local t={...};
+local e={};
+return function(...)
+for t,o in r(t)do e[o]=d(t,...);end
+a(e);
+end;
+end;
+return{
+add_handler=s;
+remove_handler=h;
+add_plugin=add_plugin;
+remove_plugin=remove_plugin;
+get_dispatcher=i;
+fire_event=n;
+get_named_arg_dispatcher=l;
+_dispatchers=o;
+_handlers=t;
+_event_map=a;
+};
+end
+return _M;
+end)
+package.preload['util.sha1']=(function(...)
+local u=string.len
+local a=string.char
+local k=string.byte
+local g=string.sub
+local m=math.floor
+local t=require"bit"
+local b=t.bnot
+local e=t.band
+local p=t.bor
+local n=t.bxor
+local o=t.lshift
+local i=t.rshift
+local h,r,d,l,c
+local function y(e,t)
+return o(e,t)+i(e,32-t)
+end
+local function s(i)
+local t,o
+local t=""
+for n=1,8 do
+o=e(i,15)
+if(o<10)then
+t=a(o+48)..t
+else
+t=a(o+87)..t
+end
+i=m(i/16)
+end
+return t
+end
+local function j(t)
+local i,o
+local n=""
+i=u(t)*8
+t=t..a(128)
+o=56-e(u(t),63)
+if(o<0)then
+o=o+64
+end
+for e=1,o do
+t=t..a(0)
+end
+for t=1,8 do
+n=a(e(i,255))..n
+i=m(i/256)
+end
+return t..n
+end
+local function q(w)
+local s,t,i,o,f,u,m,v
+local a,a
+local a={}
+while(w~="")do
+for e=0,15 do
+a[e]=0
+for t=1,4 do
+a[e]=a[e]*256+k(w,e*4+t)
+end
+end
+for e=16,79 do
+a[e]=y(n(n(a[e-3],a[e-8]),n(a[e-14],a[e-16])),1)
+end
+s=h
+t=r
+i=d
+o=l
+f=c
+for r=0,79 do
+if(r<20)then
+u=p(e(t,i),e(b(t),o))
+m=1518500249
+elseif(r<40)then
+u=n(n(t,i),o)
+m=1859775393
+elseif(r<60)then
+u=p(p(e(t,i),e(t,o)),e(i,o))
+m=2400959708
+else
+u=n(n(t,i),o)
+m=3395469782
+end
+v=y(s,5)+u+f+m+a[r]
+f=o
+o=i
+i=y(t,30)
+t=s
+s=v
+end
+h=e(h+s,4294967295)
+r=e(r+t,4294967295)
+d=e(d+i,4294967295)
+l=e(l+o,4294967295)
+c=e(c+f,4294967295)
+w=g(w,65)
+end
+end
+local function a(e,t)
+e=j(e)
+h=1732584193
+r=4023233417
+d=2562383102
+l=271733878
+c=3285377520
+q(e)
+local e=s(h)..s(r)..s(d)
+..s(l)..s(c);
+if t then
+return e;
+else
+return e:gsub("..",function(e)
+return string.char(tonumber(e,16));
+end);
+end
+end
+_G.sha1={sha1=a};
+return _G.sha1;
+end)
+package.preload['verse.plugins.tls']=(function(...)
+local o=require"util.stanza";
+local t="urn:ietf:params:xml:ns:xmpp-tls";
+function verse.plugins.tls(e)
+local function a(a)
+if e.authenticated then return;end
+if a:get_child("starttls",t)and e.conn.starttls then
+e:debug("Negotiating TLS...");
+e:send(o.stanza("starttls",{xmlns=t}));
+return true;
+elseif not e.conn.starttls and not e.secure then
+e:warn("SSL libary (LuaSec) not loaded, so TLS not available");
+elseif not e.secure then
+e:debug("Server doesn't offer TLS :(");
+end
+end
+local function o(t)
+if t.name=="proceed"then
+e:debug("Server says proceed, handshake starting...");
+e.conn:starttls({mode="client",protocol="sslv23",options="no_sslv2"},true);
+end
+end
+local function i(t)
+if t=="ssl-handshake-complete"then
+e.secure=true;
+e:debug("Re-opening stream...");
+e:reopen();
+end
+end
+e:hook("stream-features",a,400);
+e:hook("stream/"..t,o);
+e:hook("status",i,400);
+return true;
+end
+end)
+package.preload['verse.plugins.sasl']=(function(...)
+local o=require"util.stanza";
+local e=require"util.xstanza";
+local t=require"mime".b64;
+local a="urn:ietf:params:xml:ns:xmpp-sasl";
+function verse.plugins.sasl(e)
+local function i(i)
+if e.authenticated then return;end
+e:debug("Authenticating with SASL...");
+local t=t("\0"..e.username.."\0"..e.password);
+e:debug("Selecting PLAIN mechanism...");
+local a=o.stanza("auth",{xmlns=a,mechanism="PLAIN"});
+if t then
+a:text(t);
+end
+e:send(a);
+return true;
+end
+local function o(t)
+if t.name=="success"then
+e.authenticated=true;
+e:event("authentication-success");
+elseif t.name=="failure"then
+local t=t.tags[1];
+e:event("authentication-failure",{condition=t.name});
+end
+e:reopen();
+return true;
+end
+e:hook("stream-features",i,300);
+e:hook("stream/"..a,o);
+return true;
+end
+end)
+package.preload['verse.plugins.bind']=(function(...)
+local t=require"util.stanza";
+local a="urn:ietf:params:xml:ns:xmpp-bind";
+function verse.plugins.bind(e)
+local function i(o)
+if e.bound then return;end
+e:debug("Binding resource...");
+e:send_iq(t.iq({type="set"}):tag("bind",{xmlns=a}):tag("resource"):text(e.resource),
+function(t)
+if t.attr.type=="result"then
+local t=t
+:get_child("bind",a)
+:get_child("jid")
+:get_text();
+e.username,e.host,e.resource=jid.split(t);
+e.jid,e.bound=t,true;
+e:event("bind-success",full_jid);
+elseif t.attr.type=="error"then
+local a=t:child_with_name("error");
+local o,a,t=t:get_error();
+e:event("bind-failure",{error=a,text=t,type=o});
+end
+end);
+end
+e:hook("stream-features",i,200);
+return true;
+end
+end)
+package.preload['verse.plugins.version']=(function(...)
+local t="jabber:iq:version";
+local function a(e,t)
+e.name=t.name;
+e.version=t.version;
+e.platform=t.platform;
+end
+function verse.plugins.version(e)
+e.version={set=a};
+e:hook("iq/"..t,function(a)
+if a.attr.type~="get"then return;end
+local t=verse.reply(a)
+:tag("query",{xmlns=t});
+if e.version.name then
+t:tag("name"):text(tostring(e.version.name)):up();
+end
+if e.version.version then
+t:tag("version"):text(tostring(e.version.version)):up()
+end
+if e.version.platform then
+t:tag("os"):text(e.version.platform);
+end
+e:send(t);
+return true;
+end);
+function e:query_version(o,a)
+a=a or function(t)return e:event("version/response",t);end
+e:send_iq(verse.iq({type="get",to=o})
+:tag("query",{xmlns=t}),
+function(o)
+local e=o:get_child("query",t);
+if o.attr.type=="result"then
+local o=e:get_child("name");
+local t=e:get_child("version");
+local e=e:get_child("os");
+a({
+name=o and o:get_text()or nil;
+version=t and t:get_text()or nil;
+platform=e and e:get_text()or nil;
+});
+else
+local e,t,o=o:get_error();
+a({
+error=true;
+condition=t;
+text=o;
+type=e;
+});
+end
+end);
+end
+return true;
+end
+end)
+package.preload['verse.plugins.ping']=(function(...)
+require"util.xstanza";
+local o="urn:xmpp:ping";
+function verse.plugins.ping(t)
+function t:ping(e,a)
+local n=socket.gettime();
+t:send_iq(verse.iq{to=e,type="get"}:tag("ping",{xmlns=o}),
+function(t)
+if t.attr.type=="error"then
+local i,t,o=t:get_error();
+if t~="service-unavailable"and t~="feature-not-implemented"then
+a(nil,e,{type=i,condition=t,text=o});
+return;
+end
+end
+a(socket.gettime()-n,e);
+end);
+end
+return true;
+end
+end)
+package.preload['verse.plugins.session']=(function(...)
+local i=require"util.stanza";
+local a="urn:ietf:params:xml:ns:xmpp-session";
+function verse.plugins.session(e)
+local function o(t)
+local t=t:get_child("session",a);
+if t and not t:get_child("optional")then
+local function o(t)
+e:debug("Establishing Session...");
+e:send_iq(i.iq({type="set"}):tag("session",{xmlns=a}),
+function(t)
+if t.attr.type=="result"then
+e:event("session-success");
+elseif t.attr.type=="error"then
+local a=t:child_with_name("error");
+local a,t,o=t:get_error();
+e:event("session-failure",{error=t,text=o,type=a});
+end
+end);
+return true;
+end
+e:hook("bind-success",o);
+end
+end
+e:hook("stream-features",o);
+return true;
+end
+end)
+package.preload['verse.plugins.compression']=(function(...)
+local t=require"util.stanza";
+local e=require"zlib";
+local a="http://jabber.org/features/compress"
+local a="http://jabber.org/protocol/compress"
+local o="http://etherx.jabber.org/streams";
+local i=9;
+local function s(o)
+local i,e=pcall(e.deflate,i);
+if i==false then
+local t=t.stanza("failure",{xmlns=a}):tag("setup-failed");
+o:send(t);
+o:error("Failed to create zlib.deflate filter: %s",tostring(e));
+return
+end
+return e
+end
+local function h(o)
+local i,e=pcall(e.inflate);
+if i==false then
+local t=t.stanza("failure",{xmlns=a}):tag("setup-failed");
+o:send(t);
+o:error("Failed to create zlib.inflate filter: %s",tostring(e));
+return
+end
+return e
+end
+local function n(e,o)
+function e:send(a)
+local o,a,i=pcall(o,tostring(a),'sync');
+if o==false then
+e:close({
+condition="undefined-condition";
+text=a;
+extra=t.stanza("failure",{xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+});
+e:warn("Compressed send failed: %s",tostring(a));
+return;
+end
+e.conn:write(a);
+end;
+end
+local function i(e,a)
+local o=e.data
+e.data=function(i,n)
+e:debug("Decompressing data...");
+local n,a,s=pcall(a,n);
+if n==false then
+e:close({
+condition="undefined-condition";
+text=a;
+extra=t.stanza("failure",{xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+});
+stream:warn("%s",tostring(a));
+return;
+end
+return o(i,a);
+end;
+end
+function verse.plugins.compression(e)
+local function r(o)
+if not e.compressed then
+local o=o:child_with_name("compression");
+if o then
+for o in o:children()do
+local o=o[1]
+if o=="zlib"then
+e:send(t.stanza("compress",{xmlns=a}):tag("method"):text("zlib"))
+e:debug("Enabled compression using zlib.")
+return true;
+end
+end
+session:debug("Remote server supports no compression algorithm we support.")
+end
+end
+end
+local function o(t)
+if t.name=="compressed"then
+e:debug("Activating compression...")
+local t=s(e);
+if not t then return end
+local a=h(e);
+if not a then return end
+n(e,t);
+i(e,a);
+e.compressed=true;
+e:reopen();
+elseif t.name=="failure"then
+e:warn("Failed to establish compression");
+end
+end
+e:hook("stream-features",r,250);
+e:hook("stream/"..a,o);
+end
+end)
+package.preload['verse.plugins.blocking']=(function(...)
+local a="urn:xmpp:blocking";
+function verse.plugins.blocking(e)
+e.blocking={};
+function e.blocking:block_jid(o,t)
+e:send_iq(verse.iq{type="set"}
+:tag("block",{xmlns=a})
+:tag("item",{jid=o})
+,function()return t and t(true);end
+,function()return t and t(false);end
+);
+end
+function e.blocking:unblock_jid(o,t)
+e:send_iq(verse.iq{type="set"}
+:tag("unblock",{xmlns=a})
+:tag("item",{jid=o})
+,function()return t and t(true);end
+,function()return t and t(false);end
+);
+end
+function e.blocking:unblock_all_jids(t)
+e:send_iq(verse.iq{type="set"}
+:tag("unblock",{xmlns=a})
+,function()return t and t(true);end
+,function()return t and t(false);end
+);
+end
+function e.blocking:get_blocked_jids(t)
+e:send_iq(verse.iq{type="get"}
+:tag("blocklist",{xmlns=a})
+,function(e)
+local a=e:get_child("blocklist",a);
+if not a then return t and t(false);end
+local e={};
+for t in a:childtags()do
+e[#e+1]=t.attr.jid;
+end
+return t and t(e);
+end
+,function(e)return t and t(false);end
+);
+end
+end
+end)
+package.preload['verse.plugins.proxy65']=(function(...)
+local e=require"util.events";
+local h=require"util.uuid";
+local r=require"util.sha1";
+local i={};
+i.__index=i;
+local o="http://jabber.org/protocol/bytestreams";
+local n;
+function verse.plugins.proxy65(t)
+t.proxy65=setmetatable({stream=t},i);
+t.proxy65.available_streamhosts={};
+local e=0;
+t:hook("disco/service-discovered/proxy",function(a)
+if a.type=="bytestreams"then
+e=e+1;
+t:send_iq(verse.iq({to=a.jid,type="get"})
+:tag("query",{xmlns=o}),function(a)
+e=e-1;
+if a.attr.type=="result"then
+local e=a:get_child("query",o)
+:get_child("streamhost").attr;
+t.proxy65.available_streamhosts[e.jid]={
+jid=e.jid;
+host=e.host;
+port=tonumber(e.port);
+};
+end
+if e==0 then
+t:event("proxy65/discovered-proxies",t.proxy65.available_streamhosts);
+end
+end);
+end
+end);
+t:hook("iq/"..o,function(a)
+local e=verse.new(nil,{
+initiator_jid=a.attr.from,
+streamhosts={},
+current_host=0;
+});
+for t in a.tags[1]:childtags()do
+if t.name=="streamhost"then
+table.insert(e.streamhosts,t.attr);
+end
+end
+local function o()
+if e.current_host<#e.streamhosts then
+e.current_host=e.current_host+1;
+e:connect(
+e.streamhosts[e.current_host].host,
+e.streamhosts[e.current_host].port
+);
+n(t,e,a.tags[1].attr.sid,a.attr.from,t.jid);
+return true;
+end
+e:unhook("disconnected",o);
+t:send(verse.error_reply(a,"cancel","item-not-found"));
+end
+function e:accept()
+e:hook("disconnected",o,100);
+e:hook("connected",function()
+e:unhook("disconnected",o);
+local e=verse.reply(a)
+:tag("query",a.tags[1].attr)
+:tag("streamhost-used",{jid=e.streamhosts[e.current_host].jid});
+t:send(e);
+end,100);
+o();
+end
+function e:refuse()
+end
+t:event("proxy65/request",e);
+end);
+end
+function i:new(t,s)
+local e=verse.new(nil,{
+target_jid=t;
+bytestream_sid=h.generate();
+});
+local a=verse.iq{type="set",to=t}
+:tag("query",{xmlns=o,mode="tcp",sid=e.bytestream_sid});
+for t,e in ipairs(s or self.proxies)do
+a:tag("streamhost",e):up();
+end
+self.stream:send_iq(a,function(a)
+if a.attr.type=="error"then
+local a,o,t=a:get_error();
+e:event("connection-failed",{conn=e,type=a,condition=o,text=t});
+else
+local a=a.tags[1]:get_child("streamhost-used");
+if not a then
+end
+e.streamhost_jid=a.attr.jid;
+local a,i;
+for o,t in ipairs(s or self.proxies)do
+if t.jid==e.streamhost_jid then
+a,i=t.host,t.port;
+break;
+end
+end
+if not(a and i)then
+end
+e:connect(a,i);
+local function a()
+e:unhook("connected",a);
+local t=verse.iq{to=e.streamhost_jid,type="set"}
+:tag("query",{xmlns=o,sid=e.bytestream_sid})
+:tag("activate"):text(t);
+self.stream:send_iq(t,function(t)
+if t.attr.type=="result"then
+e:event("connected",e);
+else
+end
+end);
+return true;
+end
+e:hook("connected",a,100);
+n(self.stream,e,e.bytestream_sid,self.stream.jid,t);
+end
+end);
+return e;
+end
+function n(i,e,a,t,o)
+local t=r.sha1(a..t..o);
+local function a()
+e:unhook("connected",a);
+return true;
+end
+local function o(t)
+e:unhook("incoming-raw",o);
+if t:sub(1,2)~="\005\000"then
+return e:event("error","connection-failure");
+end
+e:event("connected");
+return true;
+end
+local function i(a)
+e:unhook("incoming-raw",i);
+if a~="\005\000"then
+local t="version-mismatch";
+if a:sub(1,1)=="\005"then
+t="authentication-failure";
+end
+return e:event("error",t);
+end
+e:send(string.char(5,1,0,3,#t)..t.."\0\0");
+e:hook("incoming-raw",o,100);
+return true;
+end
+e:hook("connected",a,200);
+e:hook("incoming-raw",i,100);
+e:send("\005\001\000");
+end
+end)
+package.preload['verse.plugins.jingle']=(function(...)
+local e=require"util.sha1".sha1;
+local n=require"util.stanza";
+local e=require"util.timer";
+local a=require"util.uuid".generate;
+local o="urn:xmpp:jingle:1";
+local h="urn:xmpp:jingle:errors:1";
+local t={};
+t.__index=t;
+local e={};
+local e={};
+function verse.plugins.jingle(e)
+e:hook("ready",function()
+e:add_disco_feature(o);
+end,10);
+function e:jingle(o)
+return verse.eventable(setmetatable(base or{
+role="initiator";
+peer=o;
+sid=a();
+stream=e;
+},t));
+end
+function e:register_jingle_transport(e)
+end
+function e:register_jingle_content_type(e)
+end
+local function u(i)
+local r=i:get_child("jingle",o);
+local a=r.attr.sid;
+local s=r.attr.action;
+local a=e:event("jingle/"..a,i);
+if a==true then
+e:send(verse.reply(i));
+return true;
+end
+if s~="session-initiate"then
+local t=n.error_reply(i,"cancel","item-not-found")
+:tag("unknown-session",{xmlns=h}):up();
+e:send(t);
+return;
+end
+local l=r.attr.sid;
+local a=verse.eventable{
+role="receiver";
+peer=i.attr.from;
+sid=l;
+stream=e;
+};
+setmetatable(a,t);
+local s;
+local d,h;
+for t in r:childtags()do
+if t.name=="content"and t.attr.xmlns==o then
+local i=t:child_with_name("description");
+local o=i.attr.xmlns;
+if o then
+local e=e:event("jingle/content/"..o,a,i);
+if e then
+d=e;
+end
+end
+local o=t:child_with_name("transport");
+local i=o.attr.xmlns;
+h=e:event("jingle/transport/"..i,a,o);
+if d and h then
+s=t;
+break;
+end
+end
+end
+if not d then
+e:send(n.error_reply(i,"cancel","feature-not-implemented","The specified content is not supported"));
+return;
+end
+if not h then
+e:send(n.error_reply(i,"cancel","feature-not-implemented","The specified transport is not supported"));
+return;
+end
+e:send(n.reply(i));
+a.content_tag=s;
+a.creator,a.name=s.attr.creator,s.attr.name;
+a.content,a.transport=d,h;
+function a:decline()
+end
+e:hook("jingle/"..l,function(e)
+if e.attr.from~=a.peer then
+return false;
+end
+local e=e:get_child("jingle",o);
+return a:handle_command(e);
+end);
+e:event("jingle",a);
+return true;
+end
+function t:handle_command(a)
+local t=a.attr.action;
+e:debug("Handling Jingle command: %s",t);
+if t=="session-terminate"then
+self:destroy();
+elseif t=="session-accept"then
+self:handle_accepted(a);
+elseif t=="transport-info"then
+e:debug("Handling transport-info");
+self.transport:info_received(a);
+elseif t=="transport-replace"then
+e:error("Peer wanted to swap transport, not implemented");
+else
+e:warn("Unhandled Jingle command: %s",t);
+return nil;
+end
+return true;
+end
+function t:send_command(a,e,t)
+local e=n.iq({to=self.peer,type="set"})
+:tag("jingle",{
+xmlns=o,
+sid=self.sid,
+action=a,
+initiator=self.role=="initiator"and self.stream.jid or nil,
+responder=self.role=="responder"and self.jid or nil,
+})
+:tag("content",{creator=self.creator,name=self.name})
+:add_child(e);
+if not t then
+self.stream:send(e);
+else
+self.stream:send_iq(e,t);
+end
+end
+function t:accept(a)
+local t=n.iq({to=self.peer,type="set"})
+:tag("jingle",{
+xmlns=o,
+sid=self.sid,
+action="session-accept",
+responder=e.jid,
+})
+:tag("content",{creator=self.creator,name=self.name});
+local o=self.content:generate_accept(self.content_tag:child_with_name("description"),a);
+t:add_child(o);
+local a=self.transport:generate_accept(self.content_tag:child_with_name("transport"),a);
+t:add_child(a);
+local a=self;
+e:send_iq(t,function(t)
+if t.attr.type=="error"then
+local a,t,a=t:get_error();
+e:error("session-accept rejected: %s",t);
+return false;
+end
+a.transport:connect(function(t)
+e:warn("CONNECTED (receiver)!!!");
+a.state="active";
+a:event("connected",t);
+end);
+end);
+end
+e:hook("iq/"..o,u);
+return true;
+end
+function t:offer(t,a)
+local e=n.iq({to=self.peer,type="set"})
+:tag("jingle",{xmlns=o,action="session-initiate",
+initiator=self.stream.jid,sid=self.sid});
+e:tag("content",{creator=self.role,name=t});
+local t=self.stream:event("jingle/describe/"..t,a);
+if not t then
+return false,"Unknown content type";
+end
+e:add_child(t);
+local t=self.stream:event("jingle/transport/".."urn:xmpp:jingle:transports:s5b:1",self);
+self.transport=t;
+e:add_child(t:generate_initiate());
+self.stream:debug("Hooking %s","jingle/"..self.sid);
+self.stream:hook("jingle/"..self.sid,function(e)
+if e.attr.from~=self.peer then
+return false;
+end
+local e=e:get_child("jingle",o);
+return self:handle_command(e)
+end);
+self.stream:send_iq(e,function(e)
+if e.type=="error"then
+self.state="terminated";
+local e,a,t=e:get_error();
+return self:event("error",{type=e,condition=a,text=t});
+end
+end);
+self.state="pending";
+end
+function t:terminate(e)
+local e=verse.stanza("reason"):tag(e or"success");
+self:send_command("session-terminate",e,function(e)
+self.state="terminated";
+self.transport:disconnect();
+self:destroy();
+end);
+end
+function t:destroy()
+self.stream:unhook("jingle/"..self.sid,self.handle_command);
+end
+function t:handle_accepted(e)
+local e=e:child_with_name("transport");
+self.transport:handle_accepted(e);
+self.transport:connect(function(e)
+print("CONNECTED (initiator)!")
+self.state="active";
+self:event("connected",e);
+end);
+end
+function t:set_source(a,o)
+local function t()
+local e,i=a();
+if e and e~=""then
+self.transport.conn:send(e);
+elseif e==""then
+return t();
+elseif e==nil then
+if o then
+self:terminate();
+end
+self.transport.conn:unhook("drained",t);
+a=nil;
+end
+end
+self.transport.conn:hook("drained",t);
+t();
+end
+function t:set_sink(t)
+self.transport.conn:hook("incoming-raw",t);
+self.transport.conn:hook("disconnected",function(e)
+self.stream:debug("Closing sink...");
+local e=e.reason;
+if e=="closed"then e=nil;end
+t(nil,e);
+end);
+end
+end)
+package.preload['verse.plugins.jingle_ft']=(function(...)
+local o=require"ltn12";
+local s=package.config:sub(1,1);
+local a="urn:xmpp:jingle:apps:file-transfer:1";
+local i="http://jabber.org/protocol/si/profile/file-transfer";
+function verse.plugins.jingle_ft(t)
+t:hook("ready",function()
+t:add_disco_feature(a);
+end,10);
+local n={name="file"};
+function n:generate_accept(t,e)
+if e and e.save_file then
+self.jingle:hook("connected",function()
+local e=o.sink.file(io.open(e.save_file,"w+"));
+self.jingle:set_sink(e);
+end);
+end
+return t;
+end
+local n={__index=n};
+t:hook("jingle/content/"..a,function(t,e)
+local e=e:get_child("offer"):get_child("file",i);
+local e={
+name=e.attr.name;
+size=tonumber(e.attr.size);
+};
+return setmetatable({jingle=t,file=e},n);
+end);
+t:hook("jingle/describe/file",function(e)
+local t;
+if e.timestamp then
+t=os.date("!%Y-%m-%dT%H:%M:%SZ",e.timestamp);
+end
+return verse.stanza("description",{xmlns=a})
+:tag("offer")
+:tag("file",{xmlns=i,
+name=e.filename,
+size=e.size,
+date=t,
+hash=e.hash,
+})
+:tag("desc"):text(e.description or"");
+end);
+function t:send_file(a,t)
+local e,i=io.open(t);
+if not e then return e,i;end
+local i=e:seek("end",0);
+e:seek("set",0);
+local o=o.source.file(e);
+local e=c:jingle(a);
+e:offer("file",{
+filename=t:match("[^"..s.."]+$");
+size=i;
+});
+e:hook("connected",function()
+e:set_source(o,true);
+end);
+return e;
+end
+end
+end)
+package.preload['verse.plugins.jingle_s5b']=(function(...)
+local a="urn:xmpp:jingle:transports:s5b:1";
+local s=require"util.sha1".sha1;
+local r=require"util.uuid".generate;
+local function h(e,i)
+local function n()
+e:unhook("connected",n);
+return true;
+end
+local function o(t)
+e:unhook("incoming-raw",o);
+if t:sub(1,2)~="\005\000"then
+return e:event("error","connection-failure");
+end
+e:event("connected");
+return true;
+end
+local function t(a)
+e:unhook("incoming-raw",t);
+if a~="\005\000"then
+local t="version-mismatch";
+if a:sub(1,1)=="\005"then
+t="authentication-failure";
+end
+return e:event("error",t);
+end
+e:send(string.char(5,1,0,3,#i)..i.."\0\0");
+e:hook("incoming-raw",o,100);
+return true;
+end
+e:hook("connected",n,200);
+e:hook("incoming-raw",t,100);
+e:send("\005\001\000");
+end
+local function i(a,e,i)
+local e=verse.new(nil,{
+streamhosts=e,
+current_host=0;
+});
+local function t(o)
+if o then
+return a(nil,o.reason);
+end
+if e.current_host<#e.streamhosts then
+e.current_host=e.current_host+1;
+e:debug("Attempting to connect to "..e.streamhosts[e.current_host].host..":"..e.streamhosts[e.current_host].port.."...");
+local t,a=e:connect(
+e.streamhosts[e.current_host].host,
+e.streamhosts[e.current_host].port
+);
+if not t then
+e:debug("Error connecting to proxy (%s:%s): %s",
+e.streamhosts[e.current_host].host,
+e.streamhosts[e.current_host].port,
+a
+);
+else
+e:debug("Connecting...");
+end
+h(e,i);
+return true;
+end
+e:unhook("disconnected",t);
+return a(nil);
+end
+e:hook("disconnected",t,100);
+e:hook("connected",function()
+e:unhook("disconnected",t);
+a(e.streamhosts[e.current_host],e);
+end,100);
+t();
+return e;
+end
+function verse.plugins.jingle_s5b(e)
+e:hook("ready",function()
+e:add_disco_feature(a);
+end,10);
+local t={};
+function t:generate_initiate()
+self.s5b_sid=r();
+local a=verse.stanza("transport",{xmlns=a,
+mode="tcp",sid=self.s5b_sid});
+local t=0;
+for o,i in pairs(e.proxy65.available_streamhosts)do
+t=t+1;
+a:tag("candidate",{jid=o,host=i.host,
+port=i.port,cid=o,priority=t,type="proxy"}):up();
+end
+e:debug("Have %d proxies",t)
+return a;
+end
+function t:generate_accept(e)
+local t={};
+self.s5b_peer_candidates=t;
+self.s5b_mode=e.attr.mode or"tcp";
+self.s5b_sid=e.attr.sid or self.jingle.sid;
+for e in e:childtags()do
+t[e.attr.cid]={
+type=e.attr.type;
+jid=e.attr.jid;
+host=e.attr.host;
+port=tonumber(e.attr.port)or 0;
+priority=tonumber(e.attr.priority)or 0;
+cid=e.attr.cid;
+};
+end
+local e=verse.stanza("transport",{xmlns=a});
+return e;
+end
+function t:connect(o)
+e:warn("Connecting!");
+local t={};
+for a,e in pairs(self.s5b_peer_candidates or{})do
+t[#t+1]=e;
+end
+if#t>0 then
+self.connecting_peer_candidates=true;
+local function n(t,e)
+self.jingle:send_command("transport-info",verse.stanza("transport",{xmlns=a,sid=self.s5b_sid})
+:tag("candidate-used",{cid=t.cid}));
+self.onconnect_callback=o;
+self.conn=e;
+end
+local e=s(self.s5b_sid..self.peer..e.jid,true);
+i(n,t,e);
+else
+e:warn("Actually, I'm going to wait for my peer to tell me its streamhost...");
+self.onconnect_callback=o;
+end
+end
+function t:info_received(t)
+e:warn("Info received");
+local o=t:child_with_name("content"):child_with_name("transport");
+if o:get_child("candidate-used")and not self.connecting_peer_candidates then
+local t=o:child_with_name("candidate-used");
+if t then
+local function n(o,e)
+if self.jingle.role=="initiator"then
+self.jingle.stream:send_iq(verse.iq({to=o.jid,type="set"})
+:tag("query",{xmlns=xmlns_bytestreams,sid=self.s5b_sid})
+:tag("activate"):text(self.jingle.peer),function(o)
+if o.attr.type=="result"then
+self.jingle:send_command("transport-info",verse.stanza("transport",{xmlns=a,sid=self.s5b_sid})
+:tag("activated",{cid=t.attr.cid}));
+self.conn=e;
+self.onconnect_callback(e);
+else
+self.jingle.stream:error("Failed to activate bytestream");
+end
+end);
+end
+end
+self.jingle.stream:debug("CID: %s",self.jingle.stream.proxy65.available_streamhosts[t.attr.cid]);
+local t={
+self.jingle.stream.proxy65.available_streamhosts[t.attr.cid];
+};
+local e=s(self.s5b_sid..e.jid..self.peer,true);
+i(n,t,e);
+end
+elseif o:get_child("activated")then
+self.onconnect_callback(self.conn);
+end
+end
+function t:disconnect()
+if self.conn then
+self.conn:close();
+end
+end
+function t:handle_accepted(e)
+end
+local t={__index=t};
+e:hook("jingle/transport/"..a,function(e)
+return setmetatable({
+role=e.role,
+peer=e.peer,
+stream=e.stream,
+jingle=e,
+},t);
+end);
+end
+end)
+package.preload['verse.plugins.disco']=(function(...)
+local a=require"util.stanza"
+local o=require("mime").b64
+local i=require("util.sha1").sha1
+local e="http://jabber.org/protocol/disco";
+local r=e.."#info";
+local s=e.."#items";
+function verse.plugins.disco(e)
+e.disco={cache={},info={}}
+e.disco.info.identities={
+{category='client',type='pc',name='Verse'},
+}
+e.disco.info.features={
+{var='http://jabber.org/protocol/caps'},
+{var='http://jabber.org/protocol/disco#info'},
+{var='http://jabber.org/protocol/disco#items'},
+}
+e.disco.items={}
+e.disco.nodes={}
+e.caps={}
+e.caps.node='http://code.matthewwild.co.uk/verse/'
+local function n(t,e)
+if t.category<e.category then
+return true;
+elseif e.category<t.category then
+return false;
+end
+if t.type<e.type then
+return true;
+elseif e.type<t.type then
+return false;
+end
+if(not t['xml:lang']and e['xml:lang'])or
+(e['xml:lang']and t['xml:lang']<e['xml:lang'])then
+return true
+end
+return false
+end
+local function t(t,e)
+return t.var<e.var
+end
+local function h()
+table.sort(e.disco.info.identities,n)
+table.sort(e.disco.info.features,t)
+local t=''
+for a,e in pairs(e.disco.info.identities)do
+t=t..string.format(
+'%s/%s/%s/%s',e.category,e.type,
+e['xml:lang']or'',e.name or''
+)..'<'
+end
+for a,e in pairs(e.disco.info.features)do
+t=t..e.var..'<'
+end
+return(o(i(t)))
+end
+setmetatable(e.caps,{
+__call=function(...)
+local t=h()
+return a.stanza('c',{
+xmlns='http://jabber.org/protocol/caps',
+hash='sha-1',
+node=e.caps.node,
+ver=t
+})
+end
+})
+function e:add_disco_feature(e)
+table.insert(self.disco.info.features,{var=e});
+end
+function e:jid_has_identity(t,a,e)
+local o=self.disco.cache[t];
+if not o then
+return nil,"no-cache";
+end
+local t=self.disco.cache[t].identities;
+if e then
+return t[a.."/"..e]or false;
+end
+for e in pairs(t)do
+if e:match("^(.*)/")==a then
+return true;
+end
+end
+end
+function e:jid_supports(e,t)
+local e=self.disco.cache[e];
+if not e or not e.features then
+return nil,"no-cache";
+end
+return e.features[t]or false;
+end
+function e:get_local_services(o,a)
+local e=self.disco.cache[self.host];
+if not(e)or not(e.items)then
+return nil,"no-cache";
+end
+local t={};
+for i,e in ipairs(e.items)do
+if self:jid_has_identity(e.jid,o,a)then
+table.insert(t,e.jid);
+end
+end
+return t;
+end
+function e:disco_local_services(a)
+self:disco_items(self.host,nil,function(t)
+local e=0;
+local function o()
+e=e-1;
+if e==0 then
+return a(t);
+end
+end
+for a,t in ipairs(t)do
+if t.jid then
+e=e+1;
+self:disco_info(t.jid,nil,o);
+end
+end
+if e==0 then
+return a(t);
+end
+end);
+end
+function e:disco_info(e,t,n)
+local a=verse.iq({to=e,type="get"})
+:tag("query",{xmlns=r,node=t});
+self:send_iq(a,function(a)
+if a.attr.type=="error"then
+return n(nil,a:get_error());
+end
+local o,i={},{};
+for e in a:get_child("query",r):childtags()do
+if e.name=="identity"then
+o[e.attr.category.."/"..e.attr.type]=e.attr.name or true;
+elseif e.name=="feature"then
+i[e.attr.var]=true;
+end
+end
+if not self.disco.cache[e]then
+self.disco.cache[e]={nodes={}};
+end
+if t then
+if not self.disco.cache.nodes[t]then
+self.disco.cache.nodes[t]={nodes={}};
+end
+self.disco.cache[e].nodes[t].identities=o;
+self.disco.cache[e].nodes[t].features=i;
+else
+self.disco.cache[e].identities=o;
+self.disco.cache[e].features=i;
+end
+return n(self.disco.cache[e]);
+end);
+end
+function e:disco_items(a,t,i)
+local o=verse.iq({to=a,type="get"})
+:tag("query",{xmlns=s,node=t});
+self:send_iq(o,function(o)
+if o.attr.type=="error"then
+return i(nil,o:get_error());
+end
+local e={};
+for t in o:get_child("query",s):childtags()do
+if t.name=="item"then
+table.insert(e,{
+name=t.attr.name;
+jid=t.attr.jid;
+});
+end
+end
+if not self.disco.cache[a]then
+self.disco.cache[a]={nodes={}};
+end
+if t then
+if not self.disco.cache.nodes[t]then
+self.disco.cache.nodes[t]={nodes={}};
+end
+self.disco.cache.nodes[t].items=e;
+else
+self.disco.cache[a].items=e;
+end
+return i(e);
+end);
+end
+e:hook("iq/http://jabber.org/protocol/disco#info",function(t)
+if t.attr.type=='get'then
+local o=t:child_with_name('query')
+if not o then return;end
+local s
+local i
+if o.attr.node then
+local h=h()
+local n=e.disco.nodes[o.attr.node]
+if n and n.info then
+s=n.info.identities or{}
+i=n.info.identities or{}
+elseif o.attr.node==e.caps.node..'#'..h then
+s=e.disco.info.identities
+i=e.disco.info.features
+else
+local t=a.stanza('iq',{
+to=t.attr.from,
+from=t.attr.to,
+id=t.attr.id,
+type='error'
+})
+t:tag('query',{xmlns='http://jabber.org/protocol/disco#info'}):reset()
+t:tag('error',{type='cancel'}):tag(
+'item-not-found',{xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'}
+)
+e:send(t)
+return true
+end
+else
+s=e.disco.info.identities
+i=e.disco.info.features
+end
+local o=a.stanza('query',{
+xmlns='http://jabber.org/protocol/disco#info',
+node=o.attr.node
+})
+for t,e in pairs(s)do
+o:tag('identity',e):reset()
+end
+for a,t in pairs(i)do
+o:tag('feature',t):reset()
+end
+e:send(a.stanza('iq',{
+to=t.attr.from,
+from=t.attr.to,
+id=t.attr.id,
+type='result'
+}):add_child(o))
+return true
+end
+end);
+e:hook("iq/http://jabber.org/protocol/disco#items",function(t)
+if t.attr.type=='get'then
+local o=t:child_with_name('query')
+if not o then return;end
+local i
+if o.attr.node then
+local o=e.disco.nodes[o.attr.node]
+if o then
+i=o.items or{}
+else
+local t=a.stanza('iq',{
+to=t.attr.from,
+from=t.attr.to,
+id=t.attr.id,
+type='error'
+})
+t:tag('query',{xmlns='http://jabber.org/protocol/disco#items'}):reset()
+t:tag('error',{type='cancel'}):tag(
+'item-not-found',{xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'}
+)
+e:send(t)
+return true
+end
+else
+i=e.disco.items
+end
+local o=a.stanza('query',{
+xmlns='http://jabber.org/protocol/disco#items',
+node=o.attr.node
+})
+for a,t in pairs(i)do
+o:tag('item',t):reset()
+end
+e:send(a.stanza('iq',{
+to=t.attr.from,
+from=t.attr.to,
+id=t.attr.id,
+type='result'
+}):add_child(o))
+return true
+end
+end);
+e:hook("ready",function()
+e:disco_local_services(function(t)
+for a,t in ipairs(t)do
+for a in pairs(e.disco.cache[t.jid].identities)do
+local a,o=a:match("^(.*)/(.*)$");
+e:event("disco/service-discovered/"..a,{
+type=o,jid=t.jid;
+});
+end
+end
+end);
+end,5);
+end
+end)
+package.preload['verse.client']=(function(...)
+local t=require"verse";
+local a=t.stream_mt;
+local h=require"util.jid".split;
+local s=require"lxp";
+local o=require"util.stanza";
+t.message,t.presence,t.iq,t.stanza,t.reply,t.error_reply=
+o.message,o.presence,o.iq,o.stanza,o.reply,o.error_reply;
+local r=require"core.xmlhandlers";
+local n="http://etherx.jabber.org/streams";
+local i={
+stream_ns=n,
+stream_tag="stream",
+default_ns="jabber:client"};
+function i.streamopened(e,t)
+e.stream_id=t.id;
+if not e:event("opened",t)then
+e.notopen=nil;
+end
+return true;
+end
+function i.streamclosed(e)
+return e:event("closed");
+end
+function i.handlestanza(t,e)
+if e.attr.xmlns==n then
+return t:event("stream-"..e.name,e);
+elseif e.attr.xmlns then
+return t:event("stream/"..e.attr.xmlns,e);
+end
+return t:event("stanza",e);
+end
+function a:reset()
+local e=s.new(r(self,i),"\1");
+self.parser=e;
+self.notopen=true;
+return true;
+end
+function a:connect_client(e,o)
+self.jid,self.password=e,o;
+self.username,self.host,self.resource=h(e);
+self:add_plugin("tls");
+self:add_plugin("sasl");
+self:add_plugin("bind");
+self:add_plugin("session");
+function self.data(t,e)
+local t,o=self.parser:parse(e);
+if t then return;end
+a:debug("debug","Received invalid XML (%s) %d bytes: %s",tostring(o),#e,e:sub(1,300):gsub("[\r\n]+"," "));
+a:close("xml-not-well-formed");
+end
+self:hook("incoming-raw",function(e)return self.data(self.conn,e);end);
+self.curr_id=0;
+self.tracked_iqs={};
+self:hook("stanza",function(e)
+local t,a=e.attr.id,e.attr.type;
+if t and e.name=="iq"and(a=="result"or a=="error")and self.tracked_iqs[t]then
+self.tracked_iqs[t](e);
+self.tracked_iqs[t]=nil;
+return true;
+end
+end);
+self:hook("stanza",function(e)
+if e.attr.xmlns==nil or e.attr.xmlns=="jabber:client"then
+if e.name=="iq"and(e.attr.type=="get"or e.attr.type=="set")then
+local a=e.tags[1]and e.tags[1].attr.xmlns;
+if a then
+ret=self:event("iq/"..a,e);
+if not ret then
+ret=self:event("iq",e);
+end
+end
+if ret==nil then
+self:send(t.error_reply(e,"cancel","service-unavailable"));
+return true;
+end
+else
+ret=self:event(e.name,e);
+end
+end
+return ret;
+end,-1);
+local function e()
+self:event("ready");
+end
+self:hook("session-success",e,-1)
+self:hook("bind-success",e,-1);
+local e=self.close;
+function self:close(t)
+if not self.notopen then
+self:send("</stream:stream>");
+end
+return e(self);
+end
+self:connect(self.connect_host or self.host,self.connect_port or 5222);
+self:reopen();
+end
+function a:reopen()
+self:reset();
+self:send(o.stanza("stream:stream",{to=self.host,["xmlns:stream"]='http://etherx.jabber.org/streams',
+xmlns="jabber:client",version="1.0"}):top_tag());
+end
+function a:send_iq(e,a)
+local t=self:new_id();
+self.tracked_iqs[t]=a;
+e.attr.id=t;
+self:send(e);
+end
+function a:new_id()
+self.curr_id=self.curr_id+1;
+return tostring(self.curr_id);
+end
+end)
+pcall(require,"luarocks.require");
+pcall(require,"ssl");
+local a=require"net.server";
+local n=require"util.events";
+module("verse",package.seeall);
+local t=_M;
+local e={};
+e.__index=e;
+stream_mt=e;
+t.plugins={};
+function t.new(o,a)
+local e=setmetatable(a or{},e);
+e.id=tostring(e):match("%x*$");
+e:set_logger(o,true);
+e.events=n.new();
+return e;
+end
+t.add_task=require"util.timer".add_task;
+function t.loop()
+return a.loop();
+end
+function t.quit()
+return a.setquitting(true);
+end
+t.logger=logger.init;
+function t.set_logger(e)
+a.setlogger(e);
+end
+function e:connect(e,t)
+e=e or"localhost";
+t=tonumber(t)or 5222;
+local i=socket.tcp()
+i:settimeout(0);
+local n,o=i:connect(e,t);
+if not n and o~="timeout"then
+self:warn("connect() to %s:%d failed: %s",e,t,o);
+return false,o;
+end
+local e=a.wrapclient(i,e,t,new_listener(self),"*a");
+if not e then
+return false,o;
+end
+self.conn=e;
+local t,a=e.write,tostring;
+self.send=function(i,o)return t(e,a(o));end
+return true;
+end
+function e:close()
+local e=self.conn.disconnect();
+self.conn:close();
+e(conn,reason);
+end
+function e:debug(...)
+if self.logger and self.log.debug then
+return self.logger("debug",...);
+end
+end
+function e:warn(...)
+if self.logger and self.log.warn then
+return self.logger("warn",...);
+end
+end
+function e:error(...)
+if self.logger and self.log.error then
+return self.logger("error",...);
+end
+end
+function e:set_logger(t,e)
+local a=self.logger;
+if t then
+self.logger=t;
+end
+if e then
+if e==true then
+e={"debug","info","warn","error"};
+end
+self.log={};
+for t,e in ipairs(e)do
+self.log[e]=true;
+end
+end
+return a;
+end
+function stream_mt:set_log_levels(e)
+self:set_logger(nil,e);
+end
+function e:event(e,...)
+self:debug("Firing event: "..tostring(e));
+return self.events.fire_event(e,...);
+end
+function e:hook(e,...)
+return self.events.add_handler(e,...);
+end
+function e:unhook(e,t)
+return self.events.remove_handler(e,t);
+end
+function t.eventable(t)
+t.events=n.new();
+t.hook,t.unhook=e.hook,e.unhook;
+local e=t.events.fire_event;
+function t:event(t,...)
+return e(t,...);
+end
+return t;
+end
+function e:add_plugin(e)
+if require("verse.plugins."..e)then
+local a,t=t.plugins[e](self);
+if a then
+self:debug("Loaded %s plugin",e);
+else
+self:warn("Failed to load %s plugin: %s",e,t);
+end
+end
+return self;
+end
+function new_listener(e)
+local t={};
+function t.onconnect(a)
+e.connected=true;
+e.send=function(t,e)t:debug("Sending data: "..tostring(e));return a:write(tostring(e));end;
+e:event("connected");
+end
+function t.onincoming(a,t)
+e:event("incoming-raw",t);
+end
+function t.ondisconnect(a,t)
+e.connected=false;
+e:event("disconnected",{reason=t});
+end
+function t.ondrain(t)
+e:event("drained");
+end
+function t.onstatus(a,t)
+e:event("status",t);
+end
+return t;
+end
+local e=require"util.logger".init("verse");
+return t;