diff --git a/mods/irc/init.lua b/mods/irc/init.lua index 8232c245..8a6ab85f 100755 --- a/mods/irc/init.lua +++ b/mods/irc/init.lua @@ -17,9 +17,8 @@ if not jit and package.config:sub(1, 1) == "/" then package.path = package.path.. ";/usr/share/lua/5.1/?.lua".. ";/usr/share/lua/5.1/?/init.lua" - package.cpath = package.cpath.. - -- ";/usr/lib/lua/5.1/?.so" - ";/usr/lib/x86_64-linux-gnu/lua/5.1/?.so" + package.cpath = package.cpath.. + ";/usr/lib/lua/5.1/?.so" end irc = { diff --git a/mods/irc/irc/doc/irc.luadoc b/mods/irc/irc/doc/irc.luadoc index 7bf638e6..e5616ed6 100755 --- a/mods/irc/irc/doc/irc.luadoc +++ b/mods/irc/irc/doc/irc.luadoc @@ -1,7 +1,7 @@ --- LuaIRC is a low-level IRC library for Lua. -- All functions raise Lua exceptions on error. -- --- Use new to create a new IRC object.
+-- Use new to create a new Connection object.
-- Example:

-- --require "irc"
@@ -24,11 +24,12 @@ module "irc" ---- Create a new IRC object. Use irc:connect to connect to a server. +--- Create a new Connection object. Use irc:connect to connect to a server. -- @param user Table with fields nick, username and realname. -- The nick field is required. -- --- @return Returns a new irc object. +-- @return Returns a new Connection object. +-- @see Connection function new(user) --- Hook a function to an event. @@ -49,7 +50,7 @@ function irc:unhook(name, id) function irc:connect(server, port) -- @param table Table of connection details --- @see Connection +-- @see ConnectOptions function irc:connect(table) --- Disconnect irc from the server. @@ -116,16 +117,26 @@ function irc:handle(msg) function irc:shutdown() --- Table with connection information. --- +-- @name ConnectOptions +-- @class table +-- @field host Server host name. +-- @field port Server port. [defaults to 6667] +-- @field timeout Connect timeout. [defaults to 30] +-- @field password Server password. +-- @field secure Boolean to enable TLS connection, pass a params table (described, [luasec]) to control -- [luasec]: http://www.inf.puc-rio.br/~brunoos/luasec/reference.html + +--- Class representing a connection. -- @name Connection -- @class table +-- @field authed Boolean indicating whether the connection has completed registration. +-- @field connected Whether the connection is currently connected. +-- @field motd The server's message of the day. Can be nil. +-- @field nick The current nickname. +-- @field realname The real name sent to the server. +-- @field username The username/ident sent to the server. +-- @field socket Raw socket used by the library. +-- @field supports What the server claims to support in it's ISUPPORT message. --- Class representing an IRC message. -- @name Message @@ -145,8 +156,8 @@ function irc:shutdown() --- List of hooks you can use with irc:hook. -- The parameter list describes the parameters passed to the callback function. -- -- * Event also invoked for yourself. -- † Channel passed only when user tracking is enabled diff --git a/mods/irc/irc/handlers.lua b/mods/irc/irc/handlers.lua index 6af5d107..6e60f76e 100755 --- a/mods/irc/irc/handlers.lua +++ b/mods/irc/irc/handlers.lua @@ -7,6 +7,67 @@ handlers["PING"] = function(conn, msg) conn:send(irc.Message({command="PONG", args=msg.args})) end +local function requestWanted(conn, wanted) + local args = {} + for cap, value in pairs(wanted) do + if type(value) == "string" then + cap = cap .. "=" .. value + end + if not conn.capabilities[cap] then + table.insert(args, cap) + end + end + conn:queue(irc.Message({ + command = "CAP", + args = {"REQ", table.concat(args, " ")} + }) + ) +end + +handlers["CAP"] = function(conn, msg) + local cmd = msg.args[2] + if not cmd then + return + end + if cmd == "LS" then + local list = msg.args[3] + local last = false + if list == "*" then + list = msg.args[4] + else + last = true + end + local avail = conn.availableCapabilities + local wanted = conn.wantedCapabilities + for item in list:gmatch("(%S+)") do + local eq = item:find("=", 1, true) + local k, v + if eq then + k, v = item:sub(1, eq - 1), item:sub(eq + 1) + else + k, v = item, true + end + if not avail[k] or avail[k] ~= v then + wanted[k] = conn:invoke("OnCapabilityAvailable", k, v) + end + avail[k] = v + end + if last then + if next(wanted) then + requestWanted(conn, wanted) + end + conn:invoke("OnCapabilityList", conn.availableCapabilities) + end + elseif cmd == "ACK" then + for item in msg.args[3]:gmatch("(%S+)") do + local enabled = (item:sub(1, 1) ~= "-") + local name = enabled and item or item:sub(2) + conn:invoke("OnCapabilitySet", name, enabled) + conn.capabilities[name] = enabled + end + end +end + handlers["001"] = function(conn, msg) conn.authed = true conn.nick = msg.args[1] @@ -16,7 +77,6 @@ handlers["PRIVMSG"] = function(conn, msg) conn:invoke("OnChat", msg.user, msg.args[1], msg.args[2]) end - handlers["NOTICE"] = function(conn, msg) conn:invoke("OnNotice", msg.user, msg.args[1], msg.args[2]) end @@ -71,13 +131,13 @@ handlers["NICK"] = function(conn, msg) conn:invoke("NickChange", msg.user, newNick) end if msg.user.nick == conn.nick then - conn.nick = newnick + conn.nick = newNick end end local function needNewNick(conn, msg) local newnick = conn.nickGenerator(msg.args[2]) - conn:queue(msgs.nick(newnick)) + conn:queue(irc.msgs.nick(newnick)) end -- ERR_ERRONEUSNICKNAME (Misspelt but remains for historical reasons) @@ -86,6 +146,13 @@ handlers["432"] = needNewNick -- ERR_NICKNAMEINUSE handlers["433"] = needNewNick +-- ERR_UNAVAILRESOURCE +handlers["437"] = function(conn, msg) + if not conn.authed then + needNewNick(conn, msg) + end +end + -- RPL_ISUPPORT handlers["005"] = function(conn, msg) local arglen = #msg.args diff --git a/mods/irc/irc/init.lua b/mods/irc/irc/init.lua index e2eb71e8..15c9e7c0 100755 --- a/mods/irc/irc/init.lua +++ b/mods/irc/irc/init.lua @@ -1,9 +1,11 @@ +local name = ... +name = name:gsub("%.init$", "") -local irc = require("irc.main") -require("irc.util") -require("irc.asyncoperations") -require("irc.handlers") -require("irc.messages") +local irc = require(name..".main") +require(name..".util") +require(name..".asyncoperations") +require(name..".handlers") +require(name..".messages") return irc diff --git a/mods/irc/irc/main.lua b/mods/irc/irc/main.lua index c3a7c8f6..42454fa5 100755 --- a/mods/irc/irc/main.lua +++ b/mods/irc/irc/main.lua @@ -11,12 +11,15 @@ local meta_preconnect = {} function meta_preconnect.__index(o, k) local v = rawget(meta_preconnect, k) - if not v and meta[k] then + if v == nil and meta[k] then error(("field '%s' is not accessible before connecting"):format(k), 2) end return v end +meta.connected = true +meta_preconnect.connected = false + function irc.new(data) local o = { nick = assert(data.nick, "Field 'nick' is required"); @@ -29,6 +32,9 @@ function irc.new(data) messageQueue = {}; lastThought = 0; recentMessages = 0; + availableCapabilities = {}; + wantedCapabilities = {}; + capabilities = {}; } assert(irc.checkNick(o.nick), "Erroneous nickname passed to irc.new") return setmetatable(o, meta_preconnect) @@ -57,8 +63,9 @@ function meta:invoke(name, ...) local hooks = self.hooks[name] if hooks then for id, f in pairs(hooks) do - if f(...) then - return true + local ret = f(...) + if ret then + return ret end end end @@ -109,10 +116,7 @@ function meta_preconnect:connect(_host, _port) self.socket = s setmetatable(self, meta) - self:queue(irc.Message({command="CAP", args={"REQ", "multi-prefix"}})) - - self:invoke("PreRegister", self) - self:queue(irc.Message({command="CAP", args={"END"}})) + self:invoke("PreRegister") if password then self:queue(irc.Message({command="PASS", args={password}})) @@ -142,7 +146,7 @@ end function meta:shutdown() self.socket:close() - setmetatable(self, nil) + setmetatable(self, meta_preconnect) end local function getline(self, errlevel) diff --git a/mods/irc/irc/messages.lua b/mods/irc/irc/messages.lua index 2f753edc..012f38ce 100755 --- a/mods/irc/irc/messages.lua +++ b/mods/irc/irc/messages.lua @@ -198,3 +198,7 @@ function msgs.mode(target, modes) return irc.Message({command="MODE", args={target, unpack(mt)}}) end +function msgs.cap(cmd, ...) + return irc.Message({command="CAP", args={cmd, ...}}) +end +