diff options
-rw-r--r-- | discord.lua | 24 | ||||
-rw-r--r-- | irc.lua | 129 | ||||
-rw-r--r-- | main.lua | 34 | ||||
-rw-r--r-- | rirc.lua | 66 | ||||
-rw-r--r-- | xml.lua | 80 | ||||
-rw-r--r-- | xmpp.lua | 98 |
6 files changed, 396 insertions, 35 deletions
diff --git a/discord.lua b/discord.lua new file mode 100644 index 0000000..27e0644 --- /dev/null +++ b/discord.lua @@ -0,0 +1,24 @@ +local http_request = require'http.request' +local dkjson = require'dkjson' +local url = 'https://discord.com/api/v8/webhooks/1277689699254800436/gzYU3voeunQsEdC797-hgXdTIbJDk09IVuj6l1t2alqbX9xS0d_St7bDWRkagUuQ3sat' + +local function exec_webhook(url, payload, debug) + local req = http_request.new_from_uri(url) + req.headers:upsert('content-type','application/json') + req.headers:upsert(':method','POST') + req:set_body(dkjson.encode(payload)) + local resp_head, resp_body = assert(req:go()) + local status = resp_head:get':status' + if debug then + resp_head:dump() + print() + print(resp_body:get_body_as_string()) + end + assert(status:sub(1,1) == '2', 'status was '..status..' not 2xx') +end + +return { grom = function(from,body) print(body) exec_webhook(url, { + content=body, + username=from, + }, true) end } + @@ -0,0 +1,129 @@ +local cqueues = require'cqueues' +local socket = require'cqueues.socket' +local condition = require'cqueues.condition' +local pprint = require'pprint' +local rirc = require'rirc' + +local Irc = {} + +function Irc.makepylon(pylonname, conf) + local self = {pylonname=pylonname} + local function conf_var(name) + assert(conf[name] ~= nil, 'missing conf field '..name) + self[name] = conf[name] + end + conf_var 'host' + conf_var 'port' + conf_var 'password' + conf_var 'nodename' + + self.outqueue = {} + self.outcv = condition.new() + setmetatable(self, {__index=Irc}) + return self +end + +function Irc.send(self, args) + args.source = args.source or self.nodename + rirc.send(self.sock, args) +end + +function Irc.connect(self) + self.sock = assert(socket.connect(self.host, self.port)) + self:send{'PASS', self.password, '0210-IRC', 'wilson|'} + self:send{'SERVER', self.nodename, '1', 'i am wilson'} +end + +function Irc.recving(self) + for line in self.sock:lines "*l" do + print('<', line) + local msg = rirc.parse(line) + if msg.op == 'PING' then + self:send{'PONG', msg.args[1]} + elseif msg.op == 'PRIVMSG' then + local ch = msg.args[1] + local body = msg.args[2] + local source = msg.source + local neighbors = self.network:neighbors_of(self.pylonname, ch) + pprint('gt neighbors', neighbors) + for _,other in ipairs(neighbors) do + other.pylon:enqueue { + channel = other.channel, + body = body, + source = source..'[i]' + } + end + end + end +end + +function Irc.sending(self) + local nicks_channels = {} + local function ensure_joined(nick, channel) + if not nicks_channels[nick] then + self:send{'NICK', nick, 1, 'username', 'host', 1, '+', 'realname'} + nicks_channels[nick] = {} + end + if not nicks_channels[nick][channel] then + self:send{source=nick, 'JOIN', channel} + nicks_channels[nick][channel] = true + end + end + local function say(nick, channel, body) + ensure_joined(nick, channel) + self:send{source=nick, 'PRIVMSG', channel, body} + end + + say('WILSON', '#test', 'i am wilson') + + while true do + while #self.outqueue > 0 do + local queue = self.outqueue + self.outqueue = {} + for _, outmsg in pairs(queue) do + assert(say(outmsg.source, outmsg.channel, outmsg.body)) + end + end + self.outcv:wait() + end +end + +function Irc.enqueue(self, message) + table.insert(self.outqueue, message) + self.outcv:signal() +end + +function Irc.run(self, cq, network) + self.network = network + self:connect() + cq:wrap(self.recving, self) + cq:wrap(self.sending, self) +end + + +local cq = cqueues.new() +local conf = { + host = 'localhost', + port = '6667', + password = 'mypassword', + nodename = 'wilson.ubq323', +} + +local dummy_pylon = { + enqueue = function(self, msg) + print(string.format( + "%s <%s> | %s", + msg.channel, msg.source, msg.body)) + end +} + +local network = {neighbors_of = function(self, pylonname, channel) + print('looking for neighbors of '..pylonname..', '..channel) + return { + {pylon = dummy_pylon, channel='#bridge'} + } +end} + +local pylon = Irc.makepylon('test', conf) +pylon:run(cq, network) +pprint(cq:loop()) diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..cbd98d8 --- /dev/null +++ b/main.lua @@ -0,0 +1,34 @@ +local irc=require'irc' +local xmpp=require'xmpp' +local discord=require'discord' + +local pylon_confs = { + xmpp_ubq323 = { + type='xmpp', + jid='wilson@ubq323.website', + server='ubq323.website', + auth='...', + resource='wilson', + }, + irc_apionet = { + host='localhost', + port=6667 + password='mypassword', + nodename='wilson.ubq323', + }, +} + +local pylons = {} +for name, conf in pairs(pylon_confs) do + pylons[name] = pylon_types[conf.type].make_pylon(name, conf) +end + + +local channels = { + d = { + {xmpp_ubq323, 'd@conference.ubq323.website'}, + {irc_apionet, '#d'}, + }, +} + + diff --git a/rirc.lua b/rirc.lua new file mode 100644 index 0000000..b341bfd --- /dev/null +++ b/rirc.lua @@ -0,0 +1,66 @@ +local irc = {} + +function irc.sendmsg(conn, ch, msg) + local m = msg:gsub("[\r\n]+"," ") + local l = 512-12-#ch + m = m:sub(1,l) + conn:send("PRIVMSG "..ch.." :"..m.."\r\n") +end + +function irc.send(sock, args) + local out = '' + if args.source then + out = ':' .. args.source .. ' ' + end + for i = 1, #args-1 do + local arg = tostring(args[i]) + out = out .. arg:gsub("[\r\n ]+","") .. ' ' + end + local last = tostring(args[#args]):gsub("[\r\n]+"," ") + out = out .. ':' .. last + print('>', out) + sock:write(out..'\r\n') +end + +function irc.parse(line) + local words = {} + local idx = 1 + while true do + local word,nidx = line:match("(%g+)%s*()",idx) + if not word then + break + elseif word:sub(1,1) == ":" and idx ~= 1 then + -- first word can start with : + local rest = line:sub(idx+1) + table.insert(words,rest) + break + else + table.insert(words,word) + idx = nidx + end + end + + local from = nil + if words[1] and words[1]:sub(1,1) == ":" then + -- we have a source + from = table.remove(words,1):sub(2) + end + + local op = words[1] + local args = {} + for i=2,#words do + args[i-1] = words[i] + end + return {source=from,op=op:upper(),args=args} +end + +function irc.parse_src(src) + local s, _, nick, user, host = src:find("^(.*)!(.*)@(.*)$") + if s then + return true, nick, user, host + else + return false + end +end + +return irc @@ -1,6 +1,7 @@ -- originally from http://lua-users.org/wiki/LuaXml -- modified by me a bit + local function parseargs(s) local arg = {} string.gsub(s, "([%-%w]+)=([\"'])(.-)%2", function (w, _, a) @@ -69,6 +70,7 @@ do local _, cqa = pcall(require, 'cqueues.auxlib') wrap = cqa and cqa.wrap or coroutine.wrap end + local function stanzae(getmore) return wrap(function() local buf = '' @@ -85,5 +87,81 @@ local function stanzae(getmore) end) end +local entity_escapes = { + ["<"]="<", + [">"]=">", + ["&"]="&", + ['"']=""", + ["'"]="'" +} + +local function escape(s) + return s:gsub("[<>&'\"]",entity_escapes) +end + +local safestr_mt = {name='SAFESTR', __tostring=function(x) return x.s end} +local function safestr(s) return setmetatable({s=s}, safestr_mt) end + +local function _xmlify(x) + if getmetatable(x) == safestr_mt then + return x + elseif type(x) == 'string' then + return safestr(escape(x)) + elseif type(x) == 'table' then -- must be a tag + local function argstr(t) + local argstring = '' + for k,v in pairs(t) do + argstring = argstring .. (" %s='%s'"):format(k, _xmlify(v)) + end + return argstring + end + if x.empty then + return safestr(("<%s%s/>"):format(x.label, argstr(x.xarg))) + else + local open = ("<%s%s>"):format(x.label, argstr(x.xarg)) + local close = ("</%s>"):format(x.label) + local children = {} + for _,child in ipairs(x) do + table.insert(children, tostring(_xmlify(child))) + end + return safestr(open .. table.concat(children, '') .. close) + end + end +end + +local function xmlify(...) + local n = select('#',...) + if n <= 1 then return _xmlify(...) end + + local out = {} + for i = 1, n do + out[i] = tostring(_xmlify(select(i, ...))) + end + return table.concat(out, '\n') +end + +-- dsl for input to xmlify. doesn't do escaping or anything. +-- THERE IS NO ESCAPE +local X = setmetatable({}, {__index=function(_,label) + return function(tab) + local out = {} + local xarg = {} + local empty = true + + for k, v in pairs(tab) do + if type(k) == 'number' then -- child + empty = nil + out[k] = v + elseif type(k) == 'string' then -- attrib + xarg[k] = v + end + end + + out.label = label + out.xarg = xarg + out.empty = empty + return out + end +end}) -return { psingle = psingle, stanzae = stanzae, wrap=wrap } +return { psingle = psingle, stanzae = stanzae, wrap=wrap, xmlify=xmlify, X=X } @@ -1,72 +1,102 @@ -local jid = 'wilson@ubq323.website' -local server = 'ubq323.website' -local auth = 'AHdpbHNvbgBncmVnb3J5PDM=' -local resource = 'cheese' - +local xmpp_conf = { + jid='wilson@ubq323.website', + server='ubq323.website', + auth='AHdpbHNvbgBncmVnb3J5PDM=', + resource='cheese', + muc='d@conference.ubq323.website', +} local cqueues = require'cqueues' local socket = require'cqueues.socket' +local xml = require'xml' +local X = xml.X +local xmlify = xml.xmlify +local pprint=require'pprint' +local discord=require'discord' -local function connect() - local sock = assert(socket.connect(server, 5222)) +local function connect(conf) + local sock = assert(socket.connect(conf.server, 5222)) sock:setmode('bn','bn') local start = ([[ -<?xml version='1.0'?><stream:stream from='%s' to='%s' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>]]):format(jid, server) +<?xml version='1.0'?><stream:stream from='%s' to='%s' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>]]):format(conf.jid, conf.server) -- state of the art xml parser local function check_and_send(test, text) local x = sock:read('-2048') - print(x) + assert(x:find(test)) if text then sock:write(text) end end + local function ietf_urn(v) return 'urn:ietf:params:xml:ns:xmpp-'..v end sock:write(start) - check_and_send('starttls', [[<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>]]) + check_and_send('starttls', xmlify(X.starttls{xmlns=ietf_urn"tls"})) check_and_send('proceed', nil) sock:starttls() sock:write(start) check_and_send('PLAIN', - ([[<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>]]):format(auth)) + xmlify(X.auth{xmlns=ietf_urn"sasl", mechanism='PLAIN', conf.auth})) check_and_send('success',start) - check_and_send('bind', ([[<iq type='set' id='aaa'> - <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>%s</resource></bind></iq> - ]]):format(resource)) - check_and_send('jid','<presence/>') + check_and_send('bind', + xmlify(X.iq{type='set', id='aaaa', + X.bind{xmlns=ietf_urn"bind", X.resource{conf.resource}}})) + check_and_send('jid',X.presence{X.show{'chat'}}) return sock end -local mucs = { - 'ja@conference.ubq323.website', - 'a@conference.ubq323.website', -} +-- local mucs = { +-- 'ja@conference.ubq323.website', +-- 'a@conference.ubq323.website', +-- } -local xml = require'xml' -local pprint=require'pprint' -local function run_xmpp() - local sock = connect() +local function run_xmpp(cq) + local sock = connect(xmpp_conf) + + -- sock:write(xmlify( + -- X.presence{to=xmpp_conf.muc..'/wilson', + -- X.x{xmlns='http://jabber.org/protocol/muc', + -- X.history{maxstanzas='0'}}})) + + + -- cq:wrap(function() + -- local n = 1 + -- while true do + -- sock:write(xmlify( + -- X.message{to=xmpp_conf.muc, type='groupchat', + -- id='wilson_episode_'..n, + -- X.body{'i too am in episode '..n}})) + -- n=n+1 + -- cqueues.sleep(60) + -- end + -- end) + + sock:write(xmlify( + X.iq{type='get',to=xmpp_conf.muc, id='info1', + X.query{xmlns='http://jabber.org/protocol/disco#info'}})) - for _, muc in ipairs(mucs) do - sock:write(([[ - <presence to='%s'> - <x xmlns='http://jabber.org/protocol/muc'> - <history maxstanzas='0'/> - </x> - </presence>]]):format(muc .. '/wilson')) - end for x in xml.stanzae(function() return sock:read('-2048') end) do - -- pprint(x) + pprint(x) + print(xmlify(x)) local body = x'body' and x'body'[1] - if x.label == 'message' then pprint('M', x.xarg.from, body) end + if x.label == 'message' then + pprint('M', x.xarg.from, body) + if body == 'hi wilson' then + sock:write(xmlify( + X.message{to=xmpp_conf.muc, type='groupchat', + X.body{'hi '..x.xarg.from..'.'}})) + end + -- discord.grom(x.xarg.from, body or '*(there\'s no body here)*') + end end end local cq = cqueues.new() -cq:wrap(run_xmpp) +cq:wrap(run_xmpp, cq) + pprint(cq:loop()) |