Add support for IRCv3 capabilty negotiation

This commit is contained in:
ShadowNinja 2014-08-03 13:12:34 -04:00
parent 50b068ba15
commit e216ce4b23
4 changed files with 99 additions and 20 deletions

View File

@ -1,7 +1,7 @@
--- LuaIRC is a low-level IRC library for Lua.
-- All functions raise Lua exceptions on error.
--
-- Use <code>new</code> to create a new IRC object.<br/>
-- Use <code>new</code> to create a new Connection object.<br/>
-- Example:<br/><br/>
--<code>
--require "irc"<br/>
@ -24,11 +24,12 @@
module "irc"
--- Create a new IRC object. Use <code>irc:connect</code> to connect to a server.
--- Create a new Connection object. Use <code>irc:connect</code> to connect to a server.
-- @param user Table with fields <code>nick</code>, <code>username</code> and <code>realname</code>.
-- The <code>nick</code> field is required.
--
-- @return Returns a new <code>irc</code> 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(host, port)
-- @param table Table of connection details
-- @see Connection
-- @see ConnectOptions
function irc:connect(table)
--- Disconnect <code>irc</code> from the server.
@ -116,16 +117,25 @@ function irc:handle(msg)
function irc:shutdown()
--- Table with connection information.
-- <ul>
-- <li><code>host</code> - Server host name.</li>
-- <li><code>port</code> - Server port. [defaults to <code>6667</code>]</li>
-- <li><code>timeout</code> - Connect timeout. [defaults to <code>30</code>]</li>
-- <li><code>password</code> - Server password.</li>
-- <li><code>secure</code> - Boolean to enable TLS connection, pass a params table (described, [luasec]) to control</li>
-- </ul>
-- @name ConnectOptions
-- @class table
-- @field host Server host name.
-- @field port Server port. [defaults to <code>6667</code>]
-- @field timeout Connect timeout. [defaults to <code>30</code>]
-- @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 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 +155,8 @@ function irc:shutdown()
--- List of hooks you can use with irc:hook.
-- The parameter list describes the parameters passed to the callback function.
-- <ul>
-- <li><code>PreRegister(connection)</code>Useful for CAP commands and SASL.</li>
-- <li><code>OnRaw(line) - (any non false/nil return value assumes line handled and will not be further processed)</code></li>
-- <li><code>PreRegister()</code> - Usefull for requesting capabilities.</li>
-- <li><code>OnRaw(line)</code> - Any non false/nil return value assumes line handled and will not be further processed.</li>
-- <li><code>OnSend(line)</code></li>
-- <li><code>OnDisconnect(message, errorOccurred)</code></li>
-- <li><code>OnChat(user, channel, message)</code></li>
@ -162,7 +172,10 @@ function irc:shutdown()
-- <li><code>OnUserMode(modes)</code></li>
-- <li><code>OnChannelMode(user, channel, modes)</code></li>
-- <li><code>OnModeChange(user, target, modes, ...)</code>* ('...' contains mode options such as banmasks)</li>
-- <li><code>DoX(msg)</code>'X' is any IRC command or numeric with the first letter capitalized (eg, DoPing and Do001)</li>
-- <li><code>OnCapabilityList(caps)</code></li>
-- <li><code>OnCapabilityAvailable(cap, value)</code> Called only when a capability becomes available or changes.</li>
-- <li><code>OnCapabilitySet(cap, enabled)</code>*</li>
-- <li><code>DoX(msg)</code>* - 'X' is any IRC command or numeric with the first letter capitalized (eg, DoPing and Do001)</li>
-- </ul>
-- * Event also invoked for yourself.
-- † Channel passed only when user tracking is enabled

View File

@ -8,6 +8,67 @@ handlers["PING"] = function(conn, msg)
conn:send(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(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]
@ -17,7 +78,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

View File

@ -7,6 +7,7 @@ local Message = msgs.Message
local meta = {}
meta.__index = meta
for k, v in pairs(require("irc.asyncoperations")) do
meta[k] = v
end
@ -33,6 +34,9 @@ function new(data)
messageQueue = {};
lastThought = 0;
recentMessages = 0;
availableCapabilities = {};
wantedCapabilities = {};
capabilities = {};
}
assert(util.checkNick(o.nick), "Erroneous nickname passed to irc.new")
return setmetatable(o, meta_preconnect)
@ -61,8 +65,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
@ -113,10 +118,7 @@ function meta_preconnect:connect(_host, _port)
self.socket = s
setmetatable(self, meta)
self:queue(Message({command="CAP", args={"REQ", "multi-prefix"}}))
self:invoke("PreRegister", self)
self:queue(Message({command="CAP", args={"END"}}))
if password then
self:queue(Message({command="PASS", args={password}}))

View File

@ -199,5 +199,9 @@ function m.mode(target, modes)
return Message({command="MODE", args={target, unpack(mt)}})
end
function m.cap(cmd, ...)
return Message({command="CAP", args={cmd, ...}})
end
return m