From 86f33c8e9153c1a72d4798ff82af5260bbe01661 Mon Sep 17 00:00:00 2001 From: equa Date: Thu, 10 Mar 2022 04:34:35 +0000 Subject: proper multi channel --- bot.lua | 183 +++++++++++++++++++++++++++++++++++----------------------------- mom.lua | 16 ++++-- 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 -- cgit 1.3.0-6-gf8a5