about summary refs log tree commit diff
diff options
context:
space:
mode:
authorequa <equaa@protonmail.com>2022-03-10 04:34:35 +0000
committerequa <equaa@protonmail.com>2022-03-10 04:34:35 +0000
commit86f33c8e9153c1a72d4798ff82af5260bbe01661 (patch)
tree9f7c124842d2ee56029eb8cda8f93559045c200d
parent55e859929479050e2a3b71a614a388f3b6c2a197 (diff)
proper multi channel
-rw-r--r--bot.lua183
-rw-r--r--mom.lua16
2 files changed, 111 insertions, 88 deletions
diff --git a/bot.lua b/bot.lua
index ac0a10c..63d977f 100644
--- a/bot.lua
+++ b/bot.lua
@@ -3,7 +3,6 @@ local socket = require("cqueues.socket")
 local condition = require("cqueues.condition")
 local std = require("posix.unistd")
 local stdio = require("posix.stdio")
-local dkjson = require("dkjson")
 
 local fs = require("fs")
 
@@ -75,38 +74,14 @@ end
 
 local irc_handlers = {}
 
-function handle_ed(loop, in_queue, out_queue, command)
-	local ed_out = { std.pipe() }
-	if not (ed_out[1] and ed_out[2]) then return nil, "can't create pipe" end
-	local ed_in = { std.pipe() }
-	if not (ed_in[1] and ed_in[2]) then return nil, "can't create pipe" end
-
-	local pid, err = std.fork()
-	if not pid then return nil, err end
-
-	if pid == 0 then
-		std.close(ed_out[1])
-		std.close(ed_in[2])
-		std.dup2(ed_in[1], std.STDIN_FILENO)
-		std.dup2(ed_out[2], std.STDOUT_FILENO)
-		std.execp(command[1], { select(2, table.unpack(command)) })
-		os.exit(100)
-	else
-		loop:wrap(function ()
-			local out = fs.new(ed_in[2], "w")
-			while true do
-				local x = fifo.get(in_queue)
-				print("ed: got " .. x)
-				out:write(x .. "\n")
-			end
-		end)
-		loop:wrap(function ()
-			local inp = fs.new(ed_out[1])
-			for l in inp:lines() do
-				fifo.put(out_queue, l)
-			end
-		end)
-	end
+function source_to_nick(source)
+	return string.match(source, "[^!@]*")
+end
+
+do -- test source_to_nick
+	assert(source_to_nick("a!username@host") == "a")
+	assert(source_to_nick("argh") == "argh")
+	assert(source_to_nick("man@host") == "man")
 end
 
 function irc_handlers.PING(state, line)
@@ -115,30 +90,107 @@ end
 
 function irc_handlers._001(state, line)
 	state.nick = line.params[1]
-	fifo.put(state.queue, { command = "JOIN", params = { state.channel } })
+
+	for k, v in pairs(state.config.channels) do
+		state.queue:put({ command = "JOIN", params = { k } })
+	end
+end
+
+function irc_handlers.JOIN(state, line)
+	local config = state.config
+
+	if not (line.prefix and source_to_nick(line.prefix) == state.nick) then
+		return
+	end
+
+	-- TODO several channels at once with commas
+	if not config.channels[line.params[1]] then
+		return
+	end
+
+	if not state.channels[line.params[1]] then
+		-- TODO validate the params at all
+		local ch_name = line.params[1]
+		local ch = {}
+		state.channels[ch_name] = ch
+
+		ch.command = config.channels[ch_name].command or config.command
+		ch.invoke = config.channels[ch_name].invoke or config.invoke
+
+		ch.mom_id, ch.tx_queue = state.mom:create(
+			ch.command,
+			ch_name,
+			state.rx_queue
+		)
+	end
 end
 
 function irc_handlers.PRIVMSG(state, line)
-	-- fifo.put(state.queue, { command = "NOTICE", params = {
-	-- 	state.channel,
-	-- 	"A! " .. line.params[2]
-	-- } })
-	fifo.put(state.to_ed, line.params[2])
+	-- fifo.put(state.to_ed, line.params[2])
+	if not state.channels[line.params[1]] then
+		-- TODO
+		return
+	end
+
+	state.channels[line.params[1]].tx_queue:put(
+		line.params[2]
+	)
 end
+
+function handle_ed_rx(state)
+	for line in state.rx_queue:iter() do
+		if line[2] == "line" then
+			state.queue:put({
+				command = "PRIVMSG",
+				params = {
+					line[1],
+					line[3],
+				}
+			})
+		elseif line[2] == "quit" then
+			state.queue:put({
+				command = "PART",
+				params = {
+					line[1],
+				}
+			})
+			-- we do a little trolling
+			state.loop:wrap(function()
+				cqueues.poll(10.0)
+				state.queue:put({
+					command = "JOIN",
+					params = {
+						line[1],
+						-- TODO config joins
+						-- (for keys etc)
+					},
+				})
+			end)
+		end
+	end
+end
+
 function irc_connect(loop, host, config)
 	local state = {
+		-- IRC output queue (message objects)
 		queue = fifo.new(),
+		loop = loop,
 		nick = config.nick or "ed1bot",
 		command = config.command,
+		config = config,
+		-- global rx queue from various eds
+		rx_queue = fifo.new(),
+		-- active channels! we populate this when we get a join
+		-- that we allow (i.e. that has an entry in the config)
+		-- a channel has the fields:
+		-- - invoke, a list of patterns (as in config, but being
+		--   properly populated with defaults)
+		-- - mom_id, the id in the state's process mom
+		-- - tx_queue: to send messages to the ed queue
+		--   (note we don't have rx_queue: that is shared)
+		channels = {},
 	}
 
-	-- populate channels
-	-- TODO good
-	for k in pairs(config.channels or { ["#ed1bot"] = {} }) do
-		state.channel = k
-		break
-	end
-
 	local sock = assert(socket.connect(host))
 	sock:starttls()
 
@@ -152,8 +204,6 @@ function irc_connect(loop, host, config)
 				line = { command = "BADLINE", params = { line } }
 			end
 
-			print(dkjson.encode(parse_message(raw_line)))
-
 			local command = (tonumber(line.command) and "_" or "") .. line.command
 			if irc_handlers[command] then
 				irc_handlers[command](state, line)
@@ -166,47 +216,14 @@ function irc_connect(loop, host, config)
 	loop:wrap(function ()
 		local f = fs.new(0)
 		for line in f:lines() do
-			print(line)
 			fifo.put(state.queue, parse_message(line))
 		end
 	end)
 
-	-- state.to_ed, state.from_ed = fifo.new(), fifo.new()
-	-- handle_ed(loop, state.to_ed, state.from_ed, { "red" })
 	state.mom = mom.new(loop)
-	state.to_ed, state.from_ed = select(2, state.mom:create(state.command))
-
-	loop:wrap(function()
-		while true do
-			local line = fifo.get(state.from_ed)
-			if type(line) == "string" then
-				fifo.put(state.queue, {
-					command = "PRIVMSG",
-					params = {
-						state.channel,
-						line
-					}
-				})
-			else -- TODO other possible types
-				fifo.put(state.queue, {
-					command = "PART",
-					params = {
-						state.channel
-					},
-				})
-				-- be annoying
-				cqueues.poll(10.0)
-				fifo.put(state.queue, {
-					command = "JOIN",
-					params = {
-						state.channel
-					},
-				})
-			end
-		end
-	end)
 
-	fifo.put(state.queue, { command = "NICK", params = { "ed1bot" } })
+	loop:wrap(function () handle_ed_rx(state) end)
+	fifo.put(state.queue, { command = "NICK", params = { config.nick } })
 	fifo.put(state.queue, { command = "USER", params = { "ed1bot", "0", "*", ":)" } })
 end
 
diff --git a/mom.lua b/mom.lua
index 86bfacc..4d14bd9 100644
--- a/mom.lua
+++ b/mom.lua
@@ -37,13 +37,19 @@ end
 
 -- create a new process in it
 -- returns process (unique id object), tx_queue, rx_queue
-function mom.create(m, command)
+-- you need to give it an identifier (this can be anything, and it isn't the id
+-- that it uses internally). things pushed to the rx_queue will look like
+-- { IDENTIFIER, "line", "meow" }
+-- this is to make multiplexing easier
+-- you must also give it a queue to send to (the rx_queue)
+function mom.create(m, command, rx_id, rx_queue)
 	local id = {}
 
 	m.clients[id] = {
-		command = command,
+		command = assert(command),
+		rx_id = assert(rx_id),
 		tx_queue = fifo.new(),
-		rx_queue = fifo.new(),
+		rx_queue = assert(rx_queue),
 		tx_fds = fifo.new(),
 		rx_fds = fifo.new(),
 	}
@@ -87,11 +93,11 @@ function mom.handle_from_ed(m, id)
 
 	for inp in client.rx_fds:iter() do
 		for line in inp:lines() do
-			client.rx_queue:put(line)
+			client.rx_queue:put({ client.rx_id, "line", line })
 		end
 
 		inp:close()
-		client.rx_queue:put({ "quit" })
+		client.rx_queue:put({ client.rx_id, "quit" })
 	end
 end