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") local mom = require("mom") 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", command = config.command, } -- 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" }) 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" } }) 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 }