diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .luacheckrc | 2 | ||||
-rw-r--r-- | channel.lua | 15 | ||||
-rw-r--r-- | config.lua | 45 | ||||
-rw-r--r-- | discord/consts.lua | 45 | ||||
-rw-r--r-- | discord/pylon.lua | 92 | ||||
-rw-r--r-- | discord/the.lua (renamed from discord.lua) | 10 | ||||
-rw-r--r-- | irc.lua | 119 | ||||
-rw-r--r-- | irc/pylon.lua | 75 | ||||
-rw-r--r-- | irc/rirc.lua (renamed from rirc.lua) | 7 | ||||
-rw-r--r-- | main.lua | 120 | ||||
-rw-r--r-- | pylon.lua | 55 | ||||
-rw-r--r-- | queue.lua | 6 | ||||
-rw-r--r-- | terminology.txt | 17 | ||||
-rw-r--r-- | test.lua | 24 | ||||
-rw-r--r-- | wilson.ini | 33 | ||||
-rw-r--r-- | xml_old.lua | 48 | ||||
-rw-r--r-- | xmpp/base64.lua (renamed from base64.lua) | 0 | ||||
-rw-r--r-- | xmpp/pylon.lua (renamed from xmpp.lua) | 80 | ||||
-rw-r--r-- | xmpp/sha1.lua (renamed from sha1.lua) | 0 | ||||
-rw-r--r-- | xmpp/xml.lua (renamed from xml.lua) | 9 |
21 files changed, 458 insertions, 345 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f648fef --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +wilson.ini diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..8686178 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,2 @@ +codes = true +ignore = { "6..", "21.", "23.", "31.", "4.."} diff --git a/channel.lua b/channel.lua new file mode 100644 index 0000000..e908069 --- /dev/null +++ b/channel.lua @@ -0,0 +1,15 @@ +-- channel descriptor + +local Channel = {} +function Channel.make(pylon, descriptor) + return setmetatable({pylon=pylon, descriptor=descriptor}, Channel) +end +function Channel.__eq(self, other) + return self.pylon == other.pylon and self.descriptor == other.descriptor +end +function Channel.__call(_, ...) return Channel.make(...) end +function Channel.__tostring(self) + return self.pylon.name .. ':' .. self.descriptor +end +setmetatable(Channel, Channel) +return Channel diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..c19a087 --- /dev/null +++ b/config.lua @@ -0,0 +1,45 @@ +local function partial(f,x) return function(...) return f(x,...) end end + +local function kv_syntax(block,line) + local k,v = line:match"^([a-z0-9_-]+)%s*=%s*(.*)$" + assert(k,"syntax error in kv line: "..line) + k = k:gsub("-","_") + block[k]=v +end + +local function tuple_syntax(pattern) return function(block,line) + local matches = {line:match(pattern)} + assert(matches[1], "syntax error in tuple line: "..line) + table.insert(block, matches) +end end + +local schema = { + pylon = kv_syntax, + bus = tuple_syntax"^(%S+)%s+(%S+)$", +} + +local function parse_file(file) + local blocks = {} + for k in pairs(schema) do blocks[k] = {} end + local line_handler = function() error("line outside of block") end + + for line in file:lines() do + line = line:gsub(";.*$",""):gsub("^%s*",""):gsub("%s*$","") + if line == '' then goto next end + + local block_type, block_name = line:match"%[%s*(%S+)%s+(%S+)%s*%]" + if block_type then + local blocks_of_this_type = assert(blocks[block_type],"no such block type "..block_type) + local block = {} + blocks_of_this_type[block_name] = block + line_handler = partial(schema[block_type], block) + else + line_handler(line) + end + ::next:: + end + + return blocks +end + +return {parse=parse_file} diff --git a/discord/consts.lua b/discord/consts.lua new file mode 100644 index 0000000..4727ef6 --- /dev/null +++ b/discord/consts.lua @@ -0,0 +1,45 @@ +local function flip(t) +for k,v in pairs(t) do t[v]=k end return t end + +local opcodes = flip { +[0] = "dispatch", +[1] = "heartbeat", +[2] = "identify", +[3] = "presence update", +[4] = "voice state update", +[6] = "resume", +[7] = "reconnect", +[8] = "request guild members", +[9] = "invalid session", +[10] = "hello", +[11] = "heartbeat ack", +[31] = "request soundboard sounds", +} + +local intents = flip { +guilds = 1 << 0, +guild_members = 1 << 1, +guild_moderation = 1 << 2, +guild_expressions = 1 << 3, +guild_integrations = 1 << 4, +guild_webhooks = 1 << 5, +guild_invites = 1 << 6, +guild_voice_states = 1 << 7, +guild_presences = 1 << 8, +guild_messages = 1 << 9, +guild_message_reactions = 1 << 10, +guild_message_typing = 1 << 11, +direct_messages = 1 << 12, +direct_message_reactions = 1 << 13, +direct_message_typing = 1 << 14, +message_content = 1 << 15, +guild_scheduled_events = 1 << 16, +auto_moderation_configuration = 1 << 20, +auto_moderation_execution = 1 << 21, +guild_message_polls = 1 << 24, +direct_message_polls = 1 << 25, +} +return { + opcodes = opcodes, + intents = intents, +} diff --git a/discord/pylon.lua b/discord/pylon.lua new file mode 100644 index 0000000..7f7549b --- /dev/null +++ b/discord/pylon.lua @@ -0,0 +1,92 @@ +local consts = require'discord.consts' +local opcodes = consts.opcodes +local websocket = require'http.websocket' +local json = require 'dkjson' +local Queue = require 'queue' +local exec_webhook = require'discord.the' +local cqueues = require 'cqueues' +local pylon = require 'pylon' +local pprint = require 'pprint' +local Channel = require 'channel' + +local Discord = pylon.subclass "discord" + +function Discord.init(self) + self:check_config "token" +end + +local function identify_payload(token) + local I = consts.intents + return json.encode{ + op = opcodes.identify, + d = { + properties = { + os = "wilson", browser = "wilson", device = "wilson", + }, + intents = I.guilds + I.guild_messages + I.message_content, + token = token, + } + } +end + +function Discord._connect(self) + local uri = "wss://gateway.discord.gg/?v=10&encoding=json" + self.ws = websocket.new_from_uri(uri) + assert(self.ws:connect()) + self.ws:send(identify_payload(self.token), 'text') +end + +function Discord._heartbeat(self, interval_ms) + local interval = interval_ms / 1000 + cqueues.sleep(interval * math.random()) + while true do + self.ws:send(json.encode{ + op = opcodes.heartbeat, + d = self.sequence_number, + }) + cqueues.sleep(interval) + end +end + +function Discord.recving(self) + for packet in self.ws:each() do + local event = json.decode(packet) + print(event.s, event.op, event.t) + if event.op ~= opcodes.dispatch then + pprint(event.d) + end + + if event.s then self.sequence_number = event.s end + + if event.op == opcodes.hello then + self.cq:wrap(self._heartbeat, self, event.d.heartbeat_interval) + elseif event.op == opcodes.dispatch then + self:handle_dispatch(event) + end + end +end + +function Discord.handle_dispatch(self, event) + local d = event.d + if event.t == 'MESSAGE_CREATE' then + if d.author.id == '293066066605768714' then + self.wilson:deliver(Channel(self, d.channel_id), { + body = d.content, + sender = '[d]' .. d.author.username + }) + end + end +end + +function Discord.sending(self) + for dest_channel, message in self.inbox:iter() do + print('DDD',message.sender,message.body) + exec_webhook(self.temp_wh, { + username = message.sender, + content = message.body, + }) + end +end + +return Discord + diff --git a/discord.lua b/discord/the.lua index 27e0644..d3aaf58 100644 --- a/discord.lua +++ b/discord/the.lua @@ -1,6 +1,5 @@ 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) @@ -17,8 +16,9 @@ local function exec_webhook(url, payload, debug) 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 } +return exec_webhook +-- return { grom = function(from,body) print(body) exec_webhook(url, { +-- content=body, +-- username=from, +-- }, true) end } diff --git a/irc.lua b/irc.lua deleted file mode 100644 index 541c725..0000000 --- a/irc.lua +++ /dev/null @@ -1,119 +0,0 @@ -local cqueues = require'cqueues' -local socket = require'cqueues.socket' -local pprint = require'pprint' -local rirc = require'rirc' -local Queue = require'queue' - -local Irc = {} - -function Irc._send(self, args) - args.source = args.source or self.nodename - local sent = rirc.send(self.sock, args) - print('>', sent) -end - -function Irc.makepylon(pylonname, conf, cq, network) - local self = { - pylonname=pylonname, - cq = cq, - network = network, - inbox = Queue.make(), - } - 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' - - setmetatable(self, {__index=Irc}) - return self -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.run(self) - self:_connect() - self.cq:wrap(self.recving, self) - self.cq:wrap(self.sending, self) -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 channel = msg.args[1] - local body = msg.args[2] - local source = msg.source - self.network:post(self.pylonname, channel, { - body = body, - source = source..'[i]' - }) - 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') - - for ch, msg in self.inbox:iter() do - say(msg.source, ch, msg.body) - end -end - -return Irc - --- local cq = cqueues.new() --- local conf = { --- host = 'localhost', --- port = '6667', --- password = 'mypassword', --- nodename = 'wilson.ubq323', --- } - --- local dummy_network = { --- post = function(self, pylonname, channel, message) --- pprint(pylonname, channel, message) --- end --- } - --- local pylon = Irc.makepylon('test', conf, cq, dummy_network) --- pylon:run() --- cq:wrap(function() --- local i = 0 --- while true do --- cqueues.sleep(1) --- pylon.inbox:enqueue{ --- source = 'helen', --- channel = '#test', --- body = 'i am helen '..i, --- } --- i = i + 1 --- end --- end) --- pprint('cheese', cq:loop()) diff --git a/irc/pylon.lua b/irc/pylon.lua new file mode 100644 index 0000000..0cee9aa --- /dev/null +++ b/irc/pylon.lua @@ -0,0 +1,75 @@ +local cqueues = require'cqueues' +local socket = require'cqueues.socket' +local rirc = require'irc.rirc' +local Queue = require'queue' +local Channel = require'channel' +local pylon = require'pylon' + +local Irc = pylon.subclass 'irc' + +function Irc._send(self, args) + args.source = args.source or self.nodename + local sent = rirc.send(self.sock, args) + self:log('>', sent) +end + +function Irc.init(self) + self:check_config "host port password nodename" +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 + self:log('<', line) + local msg = rirc.parse(line) + if msg.op == 'PING' then + self:_send{'PONG', msg.args[1]} + elseif msg.op == 'PRIVMSG' then + local channel_name = msg.args[1] + local body = msg.args[2] + local sender = msg.source + self.wilson:deliver(Channel(self, channel_name), { + body = body, + sender = sender..'[i]' + }) + elseif msg.op == 'ERROR' then + error(msg.args[1]) + 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') + + for dest_channel, message in self.inbox:iter() do + local channel_name = dest_channel.descriptor + say(message.sender, channel_name, message.body) + end +end + +function Irc.post(self, dest_channel, message) + self.inbox:enqueue(dest_channel, message) +end + +return Irc @@ -55,12 +55,7 @@ function irc.parse(line) 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 + return src:match"^(.*)!(.*)@(.*)$" end return irc @@ -1,86 +1,98 @@ local cqueues = require'cqueues' +local config = require'config' +local Channel = require'channel' -local pylon_types = { - irc = require'irc', - xmpp = require'xmpp', +local pylon_classes = { + irc = require'irc.pylon', + xmpp = require'xmpp.pylon', + discord = require'discord.pylon', } -local Network = {} -function Network.make(pylon_confs, bus_confs) +local Wilson = {} +function Wilson.make(config) local self = { pylons={}, busses={}, cq = cqueues.new(), } - for pylonname, conf in pairs(pylon_confs) do - local pty = pylon_types[conf.type] - self.pylons[pylonname] = pty.makepylon(pylonname, conf, self.cq, self) - print("constructed pylon",pylonname,conf.type) + for name, pylon_conf in pairs(config.pylon) do + pylon_conf.name = name + local pylon_class = pylon_classes[pylon_conf.type] + assert(pylon_class, "no such pylon type "..pylon_conf.type) + self.pylons[name] = pylon_class.make(self, pylon_conf) + print("constructed pylon",name,pylon_conf.type) end - for busname, conf in pairs(bus_confs) do + for name, bus_conf in pairs(config.bus) do local bus = {} - for _, item in ipairs(conf) do - table.insert(bus, {pylonname=item[1], channel=item[2]}) + for _, item in ipairs(bus_conf) do + local pylon_name, channel_descriptor = item[1], item[2] + local pylon = self.pylons[pylon_name] + assert(pylon,"no such pylon named "..pylon_name) + table.insert(bus, Channel(pylon, channel_descriptor)) end - self.busses[busname] = bus + print("constructed bus ",name,'with '..#bus_conf..' channels') + self.busses[name] = bus end - return setmetatable(self, {__index=Network}) + return setmetatable(self, {__index=Wilson}) end -function Network._find_bus(self, pylonname, channel) + +-- find bus containing given channel +function Wilson._find_bus(self, channel) for name, bus in pairs(self.busses) do - for _, entry in ipairs(bus) do - if entry.pylonname == pylonname and entry.channel == channel then - return bus - end - end + for _, q in ipairs(bus) do if q == channel then return bus end end end - print("warning: unfound bus for",pylonname,channel) + print("warning: unfound bus for",channel) end -function Network.post(self, pylonname, channel, message) - local bus = self:_find_bus(pylonname, channel) +function Wilson.deliver(self, source_channel, message) + local bus = self:_find_bus(source_channel) if bus then - for _, dest in ipairs(bus) do - if not (dest.pylonname == pylonname and dest.channel == channel) then - local target_pylon = self.pylons[dest.pylonname] - target_pylon.inbox:enqueue(dest.channel, message) + for _, dest_channel in ipairs(bus) do + if source_channel ~= dest_channel then + dest_channel.pylon:post(dest_channel, message) end end end end -function Network.run(self) +function Wilson.run(self) for pylonname, pylon in pairs(self.pylons) do self.cq:wrap(pylon.run, pylon) print("now running pylon", pylonname) end - print(self.cq:loop()) + print('toplevel',self.cq:loop()) end +local config_file = io.open("wilson.ini","r") +local conf = config.parse(config_file) +config_file:close() +local wilson = Wilson.make(conf) +wilson:run() + -local pylon_confs = { - xmpp_ubq323 = { - type='xmpp', - jid='wilson@ubq323.website', - server='localhost', - component='wilson.ubq323.website', - component_secret='super_secret_wilson_password', - resource='wilson', - }, - irc_local = { - type='irc', - host='localhost', - port=6667, - password='mypassword', - nodename='wilson.ubq323', - }, -} -local bus_confs = { - d = { - {'xmpp_ubq323', 'd@conference.ubq323.website'}, - {'irc_local', '#test'}, - }, -} +-- local pylon_confs = { +-- xmpp_ubq323 = { +-- type='xmpp', +-- jid='wilson@ubq323.website', +-- server='localhost', +-- component='wilson.ubq323.website', +-- component_secret='super_secret_wilson_password', +-- resource='wilson', +-- }, +-- irc_local = { +-- type='irc', +-- host='localhost', +-- port=6667, +-- password='mypassword', +-- nodename='wilson.ubq323', +-- }, +-- } +-- local bus_confs = { +-- d = { +-- {'xmpp_ubq323', 'd@conference.ubq323.website'}, +-- {'irc_local', '#test'}, +-- }, +-- } -local the_network = Network.make(pylon_confs, bus_confs) -the_network:run() +-- local the_network = Wilson.make(pylon_confs, bus_confs) +-- the_network:run() diff --git a/pylon.lua b/pylon.lua new file mode 100644 index 0000000..774395f --- /dev/null +++ b/pylon.lua @@ -0,0 +1,55 @@ +local cqueues = require 'cqueues' +local Queue = require 'queue' + +-- commonality between the different pylon classes +-- they can "inherit" from this + +local BasePylon = {} + +function BasePylon.check_config(self, vars) + for x in vars:gmatch"%S+" do + assert(self[x], "missing conf field "..x) + end +end + +function BasePylon.run(self) + self.cq = cqueues.new() + self:_connect() + self.cq:wrap(self.recving, self) + self.cq:wrap(self.sending, self) + print(self.pylon_type, self.cq:loop()) +end + +function BasePylon.post(self, dest_channel, message) + self.inbox:enqueue(dest_channel, message) +end + +function BasePylon.log(self, ...) + if self.debug then + print(self.name, ...) + end +end + +local function subclass(pylon_type) + local Subclass = {} + setmetatable(Subclass, {__index=BasePylon}) + Subclass.pylon_type = pylon_type + + Subclass.make = function(wilson, conf) + local self = setmetatable(conf, {__index=Subclass}) + for k,v in pairs { + wilson = wilson, + inbox = Queue.make(), + } do self[k] = v end + + self:init(wilson, conf) + return self + end + + return Subclass +end + +return { + BasePylon = BasePylon, + subclass = subclass, +} @@ -13,6 +13,7 @@ end function Queue.enqueue(self, ...) local item = table.pack(...) + print('q',...) table.insert(self.items, item) if #self.items > 128 then print('warning: queue is quite big') @@ -27,7 +28,10 @@ function Queue.iter(self) local items = self.items self.items = {} -- the old switcheroo for _, item in ipairs(items) do - coroutine.yield(table.unpack(item, 1, item.n)) + (function(...) + print('p',...) + coroutine.yield(...) + end)(table.unpack(item, 1, item.n)) end end self.cv:wait() diff --git a/terminology.txt b/terminology.txt new file mode 100644 index 0000000..e4157b2 --- /dev/null +++ b/terminology.txt @@ -0,0 +1,17 @@ +note: i'm writing this after not working on this project for 6 months +so i'm kind of figuring this out for myself + +anyway + +pylon: a chat server that wilson makes a connection to + which could be "some irc server", "some xmpp server" + or "discord" (discord is centralized so there's only 1 discord to connect to) + note that discord's concept of "server" aka "guild" is unrelated to anything + +channel: a particular chatroom on a particular service + eg "#a on apionet" or "#general in some discord guild" + in the code, a channel is identified by a combination of a pylon, + and a string 'channel descriptor', whose format depends on the pylon type + for instance xmpp channel descriptors are jids, discord uses numeric ids + +bus: a family of connected channels that messages will be bridged between diff --git a/test.lua b/test.lua deleted file mode 100644 index d70f7f2..0000000 --- a/test.lua +++ /dev/null @@ -1,24 +0,0 @@ -local cqueues = require'cqueues' -local condition = require'cqueues.condition' - -local cv = condition.new() -local function task1() - while true do - print('1 top') - cv:wait() - print('1 waited') - end -end -local function task2() - while true do - print('2 top') - cv:signal() - print('2 signalled') - cqueues.poll() - end -end - -local cq = cqueues.new() -cq:wrap(task1) -cq:wrap(task2) -print(cq:loop()) diff --git a/wilson.ini b/wilson.ini deleted file mode 100644 index b875b4f..0000000 --- a/wilson.ini +++ /dev/null @@ -1,33 +0,0 @@ -[pylon discord] -type=discord -token=abcdef123456 - -[pylon apionet-irc] -type=irc -host=ubq323.website -nick=wilson - -[pylon other-irc] -type=irc -host=someotherhost.example -nick=gregory - -[pylon ubq-xmpp] -type=xmpp -jid=wilson@ubq323.website -password=zyxw9876 -default-service=conference.ubq323.website - - -[channel a] -# apionet a -discord 12345678 # esoserver #apionet -discord 98765432 # apionet discord #a -ubq-xmpp a@ -apionet-irc a - -[channel ja] -discord 3141592654 # apionet discord #ja -apionet-irc ja -ubq-xmpp ja@ - diff --git a/xml_old.lua b/xml_old.lua deleted file mode 100644 index a63c0f7..0000000 --- a/xml_old.lua +++ /dev/null @@ -1,48 +0,0 @@ -function parseargs(s) - local arg = {} - string.gsub(s, "([%-%w]+)=([\"'])(.-)%2", function (w, _, a) - arg[w] = a - end) - return arg -end - -function collect(s) - local stack = {} - local top = {} - table.insert(stack, top) - local ni,c,label,xarg, empty - local i, j = 1, 1 - while true do - ni,j,c,label,xarg, empty = string.find(s, "<(%/?)([%w:]+)(.-)(%/?)>", i) - if not ni then break end - local text = string.sub(s, i, ni-1) - if not string.find(text, "^%s*$") then - table.insert(top, text) - end - if empty == "/" then -- empty element tag - table.insert(top, {label=label, xarg=parseargs(xarg), empty=1}) - elseif c == "" then -- start tag - top = {label=label, xarg=parseargs(xarg)} - table.insert(stack, top) -- new level - else -- end tag - local toclose = table.remove(stack) -- remove top - top = stack[#stack] - if #stack < 1 then - error("nothing to close with "..label) - end - if toclose.label ~= label then - error("trying to close "..toclose.label.." with "..label) - end - table.insert(top, toclose) - end - i = j+1 - end - local text = string.sub(s, i) - if not string.find(text, "^%s*$") then - table.insert(stack[#stack], text) - end - if #stack > 1 then - error("unclosed "..stack[#stack].label) - end - return stack[1] -end diff --git a/base64.lua b/xmpp/base64.lua index 4f9a967..4f9a967 100644 --- a/base64.lua +++ b/xmpp/base64.lua diff --git a/xmpp.lua b/xmpp/pylon.lua index 24a5a3c..2476fc9 100644 --- a/xmpp.lua +++ b/xmpp/pylon.lua @@ -1,42 +1,24 @@ - -local cqueues = require'cqueues' -local cqaux = require'cqueues.auxlib' local socket = require'cqueues.socket' -local xml = require'xml' +local xml = require'xmpp.xml' local X = xml.X local xmlify = xml.xmlify -local pprint=require'pprint' -local Queue = require'queue' -local base64 = require'base64' -local sha1 = require'sha1' +local base64 = require'xmpp.base64' +local sha1 = require'xmpp.sha1' +local Channel = require'channel' +local pylon = require 'pylon' -local Xmpp = {} +local Xmpp = pylon.subclass "xmpp" local function make_auth(authz, authn, password) -- sasl plain (RFC4616) return base64.encode(authz..'\0'..authn..'\0'..password) end -function Xmpp.makepylon(pylonname, conf, cq, network) - local self = { - pylonname = pylonname, - cq = cq, - network = network, - inbox = Queue.make(), - } - local function conf_var(name) - assert(conf[name] ~= nil, 'missing conf field '..name) - self[name] = conf[name] - end - conf_var 'jid' - conf_var 'server' - conf_var 'resource' - conf_var 'component' - conf_var 'component_secret' - - setmetatable(self, {__index=Xmpp}) - return self +function Xmpp.init(self) + self:check_config "server component component_secret" + self.cq = self.wilson.cq -- todo + self.nicks_inuse = {} -- todo end function Xmpp._connect_c2s(self) @@ -80,7 +62,7 @@ function Xmpp._connect_component(self) -- yes, our component name goes in the 'to' field. don't ask me why local start = ([[<stream:stream to='%s' xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams'>]]):format(self.component) - print(start) + -- print(start) -- state of the art xml parser local function check_and_send(test, text) @@ -91,7 +73,7 @@ function Xmpp._connect_component(self) sock:write(start) local streamhead = sock:read('-2048') - print('streamhead', streamhead) + -- print('streamhead', streamhead) assert(streamhead:find'accept') local streamid = streamhead:match"id='(.-)'" sock:write(xmlify(X.handshake{sha1.sha1(streamid..self.component_secret)})) @@ -99,34 +81,30 @@ function Xmpp._connect_component(self) return sock end +Xmpp._connect = Xmpp._connect_component local THE_MUC = 'd@conference.ubq323.website' -function Xmpp.run(self) - self:_connect_component() - self.cq:wrap(self.recving, self) - self.cq:wrap(self.sending, self) -end - function Xmpp.recving(self) local function getmore() local function t(...) - pprint(...) + -- pprint(...) return ... end return t(self.sock:read'-2048') end for x in xml.stanzae(getmore) do - pprint(x) - print(xmlify(x)) + -- pprint(x) + -- print(xmlify(x)) local body = x'body' and x'body'[1] if x.label == 'message' then local fr = x.xarg.from local t = x.xarg.to - if body and fr:match"/" and t == 'wilson@'..self.component then - self.network:post(self.pylonname, THE_MUC, { + local from_nick = fr:match("/(.*)") + if not self.nicks_inuse[from_nick] and body and fr:match"/" and t == 'wilson@'..self.component then + self.wilson:deliver(Channel(self, THE_MUC), { body = body, - source = '[x]'..x.xarg.from:match("/(.*)") + sender = '[x]'..from_nick }) end end @@ -135,14 +113,13 @@ end function Xmpp.sending(self) local users_inuse = {} - local nicks_inuse = {} local function ensure_joined(muc,user,nick) - if nicks_inuse[nick] then return end + if self.nicks_inuse[nick] then return end user = user:gsub("[^a-zA-Z0-9%.]","."):match("^%.*(.-)%.*$") while users_inuse[user] do user = user..'-' end local jid = user..'@'..self.component local mucjid = muc..'/'..nick - nicks_inuse[nick] = true + self.nicks_inuse[nick] = true users_inuse[user] = true self.sock:write(xmlify( @@ -151,17 +128,20 @@ function Xmpp.sending(self) X.history{maxstanzas='0'}}})) end ensure_joined(THE_MUC, 'wilson', 'wilson') - for ch, msg in self.inbox:iter() do - pprint(ch,msg) - ensure_joined(THE_MUC, msg.source, msg.source) - local user = msg.source:gsub("[^a-zA-Z0-9%.]","."):match("^%.*(.-)%.*$") + for dest_channel, message in self.inbox:iter() do + -- pprint(dest_channel, message) + ensure_joined(THE_MUC, message.sender, message.sender) + local user = message.sender:gsub("[^a-zA-Z0-9%.]","."):match("^%.*(.-)%.*$") local jid = user..'@'..self.component self.sock:write(xmlify( X.message{to=THE_MUC, type='groupchat', from=jid, - X.body{msg.body}})) + X.body{message.body}})) end end +function Xmpp.post(self, dest_channel, message) + self.inbox:enqueue(dest_channel, message) +end return Xmpp @@ -1,21 +1,20 @@ -- originally from http://lua-users.org/wiki/LuaXml -- modified by me a bit - -entity_escapes = { +local entity_escapes = { ["<"]="<", [">"]=">", ["&"]="&", ['"']=""", ["'"]="'" } -entity_unescapes = {} +local entity_unescapes = {} for k,v in pairs(entity_escapes) do entity_unescapes[v]=k end -function escape(s) +local function escape(s) return s:gsub("[<>&'\"]",entity_escapes) end -function unescape(s) +local function unescape(s) return s:gsub("&[a-z]+;",entity_unescapes) end |