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.
---
--- host
- Server host name.
--- port
- Server port. [defaults to 6667
]
--- timeout
- Connect timeout. [defaults to 30
]
--- password
- Server password.
--- secure
- Boolean to enable TLS connection, pass a params table (described, [luasec]) to control
---
+-- @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.
--
--- PreRegister(connection)
Useful for CAP commands and SASL.
--- OnRaw(line) - (any non false/nil return value assumes line handled and will not be further processed)
+-- PreRegister()
- Usefull for requesting capabilities.
+-- OnRaw(line)
- Any non false/nil return value assumes line handled and will not be further processed.
-- OnSend(line)
-- OnDisconnect(message, errorOccurred)
-- OnChat(user, channel, message)
@@ -162,7 +173,10 @@ function irc:shutdown()
-- OnUserMode(modes)
-- OnChannelMode(user, channel, modes)
-- OnModeChange(user, target, modes, ...)
* ('...' contains mode options such as banmasks)
--- DoX(msg)
'X' is any IRC command or numeric with the first letter capitalized (eg, DoPing and Do001)
+-- OnCapabilityList(caps)
+-- OnCapabilityAvailable(cap, value)
Called only when a capability becomes available or changes.
+-- OnCapabilitySet(cap, enabled)
*
+-- DoX(msg)
* - 'X' is any IRC command or numeric with the first letter capitalized (eg, DoPing and Do001)
--
-- * 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
+