summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.luacheckrc2
-rw-r--r--channel.lua15
-rw-r--r--config.lua45
-rw-r--r--discord/consts.lua45
-rw-r--r--discord/pylon.lua92
-rw-r--r--discord/the.lua (renamed from discord.lua)10
-rw-r--r--irc.lua119
-rw-r--r--irc/pylon.lua75
-rw-r--r--irc/rirc.lua (renamed from rirc.lua)7
-rw-r--r--main.lua120
-rw-r--r--pylon.lua55
-rw-r--r--queue.lua6
-rw-r--r--terminology.txt17
-rw-r--r--test.lua24
-rw-r--r--wilson.ini33
-rw-r--r--xml_old.lua48
-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
diff --git a/rirc.lua b/irc/rirc.lua
index 95445ad..0508351 100644
--- a/rirc.lua
+++ b/irc/rirc.lua
@@ -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
diff --git a/main.lua b/main.lua
index 7d8c97d..fd0d146 100644
--- a/main.lua
+++ b/main.lua
@@ -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,
+}
diff --git a/queue.lua b/queue.lua
index 8c9c373..5a30646 100644
--- a/queue.lua
+++ b/queue.lua
@@ -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
diff --git a/sha1.lua b/xmpp/sha1.lua
index 7451595..7451595 100644
--- a/sha1.lua
+++ b/xmpp/sha1.lua
diff --git a/xml.lua b/xmpp/xml.lua
index 68fa741..e54f27f 100644
--- a/xml.lua
+++ b/xmpp/xml.lua
@@ -1,21 +1,20 @@
-- originally from http://lua-users.org/wiki/LuaXml
-- modified by me a bit
-
-entity_escapes = {
+local entity_escapes = {
["<"]="&lt;",
[">"]="&gt;",
["&"]="&amp;",
['"']="&quot;",
["'"]="&apos;"
}
-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