diff options
author | ubq323 <ubq323@ubq323.website> | 2025-02-22 00:20:52 +0000 |
---|---|---|
committer | ubq323 <ubq323@ubq323.website> | 2025-02-22 00:20:52 +0000 |
commit | 989b6349b0c7e5a0d7ec06d2ca24d629c618fe12 (patch) | |
tree | c424e6eac3ee626db4b4993def6eda4b9292eb66 | |
parent | bf776624fb59d147b82d2a6a13c36292844a47b7 (diff) |
-rw-r--r-- | channel.lua | 15 | ||||
-rw-r--r-- | config.lua | 45 | ||||
-rw-r--r-- | irc.lua | 82 | ||||
-rw-r--r-- | main.lua | 115 | ||||
-rw-r--r-- | terminology.txt | 17 | ||||
-rw-r--r-- | wilson.ini | 28 | ||||
-rw-r--r-- | xml_old.lua | 48 | ||||
-rw-r--r-- | xmpp.lua | 20 |
8 files changed, 193 insertions, 177 deletions
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..a8838c8 --- /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} @@ -12,23 +12,20 @@ function Irc._send(self, args) print('>', sent) end -function Irc.makepylon(pylonname, conf, cq, network) - local self = { - pylonname=pylonname, - cq = cq, - network = network, +function Irc.check_config(self, vars) + for x in vars:gmatch("%S+") do + assert(self[x], "missing conf field "..x) end end + +function Irc.make(wilson, conf) + local self = setmetatable(conf, {__index=Irc}) + + for k,v in pairs { + wilson = wilson, 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' + } do self[k] = v end + + self:check_config "host port password nodename" - setmetatable(self, {__index=Irc}) return self end @@ -39,9 +36,11 @@ function Irc._connect(self) end function Irc.run(self) + local cq = cqueues.new() self:_connect() - self.cq:wrap(self.recving, self) - self.cq:wrap(self.sending, self) + cq:wrap(self.recving, self) + cq:wrap(self.sending, self) + print('irc',cq:loop()) end function Irc.recving(self) @@ -51,13 +50,15 @@ function Irc.recving(self) if msg.op == 'PING' then self:_send{'PONG', msg.args[1]} elseif msg.op == 'PRIVMSG' then - local channel = msg.args[1] + local channel_name = msg.args[1] local body = msg.args[2] - local source = msg.source - self.network:post(self.pylonname, channel, { + local sender = msg.source + self.wilson:deliver(Channel(self, channel_name), { body = body, - source = source..'[i]' + sender = sender..'[i]' }) + elseif msg.op == 'ERROR' then + error(msg.args[1]) end end end @@ -81,39 +82,14 @@ function Irc.sending(self) say('WILSON', '#test', 'i am wilson') - for ch, msg in self.inbox:iter() do - say(msg.source, ch, msg.body) + for dest_channel, message in self.inbox:iter() do + local channel_name = dest_channel.descriptor + say(message.sender, channel_name, message.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 --- } +function Irc.post(self, dest_channel, message) + self.inbox.enqueue(dest_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()) +return Irc @@ -1,86 +1,97 @@ local cqueues = require'cqueues' +local config = require'config' +local Channel = require'channel' -local pylon_types = { +local pylon_classes = { irc = require'irc', xmpp = require'xmpp', } -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/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 @@ -1,33 +1,35 @@ -[pylon discord] -type=discord -token=abcdef123456 +# [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 +port=6667 +password=secretpassword +nodename=wilson.ubq323 [pylon ubq-xmpp] type=xmpp jid=wilson@ubq323.website +server=ubq323.website password=zyxw9876 default-service=conference.ubq323.website +resource=wilson +component=wilson.ubq323.website +component_secret=my_secret_password -[channel a] +[bus a] # apionet a -discord 12345678 # esoserver #apionet -discord 98765432 # apionet discord #a +# discord 12345678 # esoserver #apionet +# discord 98765432 # apionet discord #a ubq-xmpp a@ apionet-irc a -[channel ja] -discord 3141592654 # apionet discord #ja +[bus 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 @@ -17,11 +17,10 @@ local function make_auth(authz, authn, password) return base64.encode(authz..'\0'..authn..'\0'..password) end -function Xmpp.makepylon(pylonname, conf, cq, network) +function Xmpp.make(wilson, conf) local self = { - pylonname = pylonname, - cq = cq, - network = network, + name = conf.name, + wilson = wilson, inbox = Queue.make(), } local function conf_var(name) @@ -36,7 +35,6 @@ function Xmpp.makepylon(pylonname, conf, cq, network) setmetatable(self, {__index=Xmpp}) return self - end function Xmpp._connect_c2s(self) @@ -124,9 +122,9 @@ function Xmpp.recving(self) 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, { + self.wilson:deliver(Channel(self, THE_MUC), { body = body, - source = '[x]'..x.xarg.from:match("/(.*)") + sender = '[x]'..x.xarg.from:match("/(.*)") }) end end @@ -151,10 +149,10 @@ 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, |