diff options
author | equa <equaa@protonmail.com> | 2022-03-07 19:14:15 +0000 |
---|---|---|
committer | equa <equaa@protonmail.com> | 2022-03-07 19:14:15 +0000 |
commit | 224e8f43c50a513bae78af2152c12c0a5f9564f9 (patch) | |
tree | 7748d6ffeba50a43d3a7f55a3c85983d07969776 /bot.lua |
initial commit
Diffstat (limited to 'bot.lua')
-rw-r--r-- | bot.lua | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/bot.lua b/bot.lua new file mode 100644 index 0000000..f957fd3 --- /dev/null +++ b/bot.lua @@ -0,0 +1,199 @@ +local cqueues = require("cqueues") +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") + +local fifo = require("fifo") + +function irc_dump(fifo, socket) + for data in fifo:iter() do + data = emit_message(data) + if not string.match(data, "[\r\n]") and #string <= 510 then + socket:write(data .. "\r\n") + end + end +end + +function parse_message(data) + if string.match(data, "[\0\r\n]") then return nil, "illegal character" end + + local pos = 1 + local prefix + local command + local params = {} + + if string.match(data, "^:") then + prefix, pos = string.match(data, "^:([^ ]+)()") + end + + command, pos = string.match(data, "([^ ]+)()", pos) + while pos < #data do + local start, param + start, param, pos = string.match(data, "()([^ ]+)()", pos) + + if not start then break end + if string.sub(param, 1, 1) == ":" then + table.insert(params, string.sub(data, start + 1)) + break + else + table.insert(params, param) + end + end + + return { params = params, command = command, prefix = prefix } +end + +function emit_message(message) + local out = {} + if message.prefix then + table.insert(out, message.prefix) + table.insert(out, " ") + end + + table.insert(out, message.command) + + for i, v in ipairs(message.params or {}) do + assert(not string.match(i, "[\0\r\n]")) + + if i == #(message.params or {}) then + table.insert(out, " :") + table.insert(out, v) + else + assert(not string.match(v, "^:")) + table.insert(out, " ") + table.insert(out, v) + end + end + + return table.concat(out) +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 +end + +function irc_handlers.PING(state, line) + fifo.put(state.queue, { command = "PONG", params = line.params }) +end + +function irc_handlers._001(state, line) + state.nick = line.params[1] + fifo.put(state.queue, { command = "JOIN", params = { state.channel } }) +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]) +end +function irc_connect(loop, host, config) + local state = { + queue = fifo.new(), + nick = config.nick or "ed1bot", + } + + -- 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() + + loop:wrap(function () irc_dump(state.queue, sock) end) + loop:wrap(function () + for raw_line in sock:lines("*l") do -- TODO set line length + local line = parse_message(raw_line) + -- TODO assert line or something + if not line then + print("bad line! " .. raw_line) + 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) + end + end + + os.exit(0) + end) + + 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" }) + + loop:wrap(function() + while true do + local line = fifo.get(state.from_ed) + fifo.put(state.queue, { + command = "PRIVMSG", + params = { + state.channel, + line + } + }) + end + end) + + fifo.put(state.queue, { command = "NICK", params = { "ed1bot" } }) + fifo.put(state.queue, { command = "USER", params = { "ed1bot", "0", "*", ":)" } }) +end + +local main = cqueues.new() +if not arg[1] then + print("usage: edbot [CONFIG]") + os.exit(1) +end +main:wrap(function () irc_connect(main, { host = "localhost", port = 6697 }, dofile(arg[1])) end) +assert(main:loop()) +return { parse_message = parse_message } |