From 2d581a2b62a398963e4e418243b10e02b8d648af Mon Sep 17 00:00:00 2001 From: equa Date: Sun, 13 Mar 2022 18:01:02 +0000 Subject: Various fixes - add directory configuration option - add debug mode for checking output - fix FIFOs not actually being FIFOs - add user and pass configuration options - validate config files - don't fail instantly if we fail to exec or chdir; instead, wait for input to avoid restart loops - have ed restart on JOIN rather than instantly - output stderr to the channel as well --- TODO | 1 - bot.lua | 68 +++++++++++++++++++++++++++++++++++++++++++++++++----- config.example.lua | 13 +++++++++-- fifo.lua | 10 ++++++-- mom.lua | 22 ++++++++++++++---- 5 files changed, 98 insertions(+), 16 deletions(-) diff --git a/TODO b/TODO index 673b2fa..028c7b4 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -have the shell cd for us (we can do this by running sh -c, but making it cd would be better) support joining irc channels with keys support empty lines throttle really long messages diff --git a/bot.lua b/bot.lua index db01ba8..0704b63 100644 --- a/bot.lua +++ b/bot.lua @@ -7,7 +7,9 @@ local fifo = require("fifo") local mom = require("mom") local function parse_message(data) - if string.match(data, "[\0\r\n]") then return nil, "illegal character" end + if string.match(data, "[\0\r\n]") then + return nil, "illegal character" + end local pos = 1 local prefix @@ -108,16 +110,23 @@ function irc_handlers.JOIN(state, line) if not state.channels[line.params[1]] then -- TODO validate the params at all local ch_name = line.params[1] + local ch_config = config.channels[ch_name] 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.command = ch_config.command or config.command + ch.invoke = ch_config.invoke or config.invoke + ch.directory = ch_config.directory or config.directory ch.mom_id, ch.tx_queue = state.mom:create( ch.command, ch_name, - state.rx_queue + state.rx_queue, + ch.directory + ) + else + state.mom:start_process( + state.channels[line.params[1]].mom_id ) end end @@ -134,6 +143,7 @@ function irc_handlers.PRIVMSG(state, line) local full_line = line.params[2] local line for _, prefix in ipairs(state.channels[ch].invoke) do + -- TODO: escape the nick itself prefix = string.gsub(prefix, "%%n", state.nick) prefix = string.gsub(prefix, "%%%", "%") -- TODO parens and stuff maybe @@ -199,8 +209,10 @@ local function irc_connect(loop, config) -- a channel has the fields: -- - invoke, a list of patterns (as in config, but being -- properly populated with defaults) + -- - command, the command to run as a list + -- - directory, the dir to run it in -- - mom_id, the id in the state's process mom - -- - tx_queue: to send messages to the ed queue + -- - tx_queue, to send messages to the ed queue -- (note we don't have rx_queue: that is shared) channels = {}, } @@ -220,6 +232,10 @@ local function irc_connect(loop, config) line = { command = "BADLINE", params = { line } } end + if line.command == "ERROR" or state.config.debug then + print(raw_line) + end + local command = (tonumber(line.command) and "_" or "") .. line.command if irc_handlers[command] then irc_handlers[command](state, line) @@ -239,14 +255,53 @@ local function irc_connect(loop, config) state.mom = mom.new(loop) loop:wrap(function () handle_ed_rx(state) end) + + if config.pass then + fifo.put(state.queue, { + command = "PASS", params = { config.pass } + }) + end + fifo.put(state.queue, { command = "NICK", params = { config.nick } }) -- TODO: config fifo.put(state.queue, { command = "USER", - params = { "ed1bot", "0", "*", ":)" }, + params = { config.user, "0", "*", ":)" }, }) end +local function validate_config(c) + local function valid(name, thing, kind) + assert(type(thing) == kind, + kind .. " " .. name .. " must be specified" + ) + end + + local function optional(name, thing, kind) + if thing then valid(name, thing, kind) end + end + + valid("host", c.host, "table") + valid("host.host", c.host.host, "string") + valid("host.port", c.host.port, "number") + valid("nick", c.nick, "string") + valid("user", c.user, "string") + optional("pass", c.pass, "string") + valid("directory", c.directory, "string") + -- TODO better + valid("command", c.command, "table") + valid("invoke", c.invoke, "table") + valid("channels", c.channels, "table") + + for k, v in pairs(c.channels) do + local name = string.format("channels[%q]", k) + valid(name, v, "table") + optional(name .. ".invoke", v.invoke, "table") + optional(name .. ".command", v.command, "table") + optional(name .. ".directory", v.directory, "string") + end +end + do local main = cqueues.new() if not arg[1] then @@ -255,6 +310,7 @@ do end local config = assert(dofile(arg[1])) + validate_config(config) main:wrap(function () irc_connect(main, config) diff --git a/config.example.lua b/config.example.lua index 1d65b8c..faafcf4 100644 --- a/config.example.lua +++ b/config.example.lua @@ -4,9 +4,18 @@ return { port = 6667, tls = false, }, - nick = "ed1bot", - -- allow dms? + -- print all messages received from the server to stdout + debug = true, + nick = "edward", + user = "edward", + -- pass = "user:pass" + -- allow dms? currently unimplemented direct = false, + -- directory to run commands under. this can also be specified + -- in the individual channels; the individual settings overwrite + -- this one and don't act relative to it. + directory = "testbed", + -- this one can be overwritten too command = { "red" }, -- a ist of lua patterns (maybe regex later) that -- tells edbot when to listen for commands diff --git a/fifo.lua b/fifo.lua index 03a7af7..f3a3d10 100644 --- a/fifo.lua +++ b/fifo.lua @@ -11,7 +11,7 @@ function fifo.signal(f) end function fifo.get(f) - if not f.head then + while not f.head do f.cond:wait() end @@ -21,7 +21,13 @@ function fifo.get(f) end function fifo.put(f, data) - f.head = { data = data, tail = f.head } + local tail = f.head + while tail and tail.tail do tail = tail.tail end + if tail then + tail.tail = { data = data } + else + f.head = { data = data } + end fifo.signal(f) end diff --git a/mom.lua b/mom.lua index ef0f4c8..3cc5696 100644 --- a/mom.lua +++ b/mom.lua @@ -41,11 +41,12 @@ end -- { 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) +function mom.create(m, command, rx_id, rx_queue, directory) local id = {} m.clients[id] = { command = assert(command), + directory = assert(directory), rx_id = assert(rx_id), tx_queue = fifo.new(), rx_queue = assert(rx_queue), @@ -59,6 +60,13 @@ function mom.create(m, command, rx_id, rx_queue) return id, m.clients[id].tx_queue, m.clients[id].rx_queue end +local function error_wait(err) + print("error: " .. err) + print("say something to try again") + io.read("*l") + os.exit(100) +end + function mom.start_process(m, id) local proc_out_rx, proc_out_tx = std.pipe() if not (proc_out_rx and proc_out_tx) then return nil, "can't create pipe" end @@ -73,8 +81,14 @@ function mom.start_process(m, id) std.close(proc_in_tx) std.dup2(proc_in_rx, std.STDIN_FILENO) std.dup2(proc_out_tx, std.STDOUT_FILENO) - std.execp(m.clients[id].command[1], { select(2, table.unpack(m.clients[id].command)) }) -- meh - os.exit(100) -- TODO + std.dup2(proc_out_tx, std.STDERR_FILENO) + local ok, err = std.chdir(m.clients[id].directory) + if not ok then + error_wait(err) + end + + local _, err = std.execp(m.clients[id].command[1], { select(2, table.unpack(m.clients[id].command)) }) -- meh + error_wait(err) else m.pids[pid] = id std.close(proc_in_rx) @@ -128,8 +142,6 @@ function mom.tend(m) if pid and status ~= "running" then local client_id = m.pids[pid] m.pids[pid] = nil - -- TODO: only restart sometimes - mom.start_process(m, client_id) m.clients[client_id].rx_queue:put({ "quit" }) end end -- cgit 1.3.0-6-gf8a5