Skip to content

Add support for arbritrary status modes via the ISUPPORT PREFIX value #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/irc.luadoc
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ 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(user, ...)</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
Expand All @@ -154,7 +155,7 @@ function irc:shutdown()
-- <li><code>username</code> - User username.</li>
-- <li><code>host</code> - User hostname.</li>
-- <li><code>realname</code> - User real name.</li>
-- <li><code>access</code> - User access, available in channel-oriented callbacks. A table containing the boolean fields 'op', 'halfop', and 'voice'.</li>
-- <li><code>access</code> - User access, available in channel-oriented callbacks. A table containing boolean fields for each access mode that the server supports. Eg: 'o', and 'v'.</li>
-- </ul>
-- Apart from <code>nick</code>, fields may be missing. To fill them in, enable user tracking and use irc:whois.
-- @name User
Expand Down
88 changes: 50 additions & 38 deletions handlers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,24 @@ module "irc"

handlers = {}

handlers["PING"] = function(o, prefix, query)
handlers["PING"] = function(o, user, query)
o:send("PONG :%s", query)
end

handlers["001"] = function(o, prefix, me)
handlers["001"] = function(o, user, me)
o.authed = true
o.nick = me
end

handlers["PRIVMSG"] = function(o, prefix, channel, message)
o:invoke("OnChat", parsePrefix(prefix), channel, message)
handlers["PRIVMSG"] = function(o, user, channel, message)
o:invoke("OnChat", user, channel, message)
end

handlers["NOTICE"] = function(o, prefix, channel, message)
o:invoke("OnNotice", parsePrefix(prefix), channel, message)
handlers["NOTICE"] = function(o, user, channel, message)
o:invoke("OnNotice", user, channel, message)
end

handlers["JOIN"] = function(o, prefix, channel)
local user = parsePrefix(prefix)
handlers["JOIN"] = function(o, user, channel)
if o.track_users then
if user.nick == o.nick then
o.channels[channel] = {users = {}}
Expand All @@ -37,8 +36,7 @@ handlers["JOIN"] = function(o, prefix, channel)
o:invoke("OnJoin", user, channel)
end

handlers["PART"] = function(o, prefix, channel, reason)
local user = parsePrefix(prefix)
handlers["PART"] = function(o, user, channel, reason)
if o.track_users then
if user.nick == o.nick then
o.channels[channel] = nil
Expand All @@ -49,8 +47,7 @@ handlers["PART"] = function(o, prefix, channel, reason)
o:invoke("OnPart", user, channel, reason)
end

handlers["QUIT"] = function(o, prefix, msg)
local user = parsePrefix(prefix)
handlers["QUIT"] = function(o, user, msg)
if o.track_users then
for channel, v in pairs(o.channels) do
v.users[user.nick] = nil
Expand All @@ -59,8 +56,7 @@ handlers["QUIT"] = function(o, prefix, msg)
o:invoke("OnQuit", user, msg)
end

handlers["NICK"] = function(o, prefix, newnick)
local user = parsePrefix(prefix)
handlers["NICK"] = function(o, user, newnick)
if o.track_users then
for channel, v in pairs(o.channels) do
local users = v.users
Expand All @@ -79,7 +75,7 @@ handlers["NICK"] = function(o, prefix, newnick)
end
end

local function needNewNick(o, prefix, target, badnick)
local function needNewNick(o, user, target, badnick)
local newnick = o.nickGenerator(badnick)
o:send("NICK %s", newnick)
end
Expand All @@ -90,85 +86,101 @@ handlers["432"] = needNewNick
-- ERR_NICKNAMEINUSE
handlers["433"] = needNewNick

-- RPL_ISUPPORT
handlers["005"] = function(o, prefix, nick, ...)
local list = {...}
local listlen = #list
-- Skip last parameter (info)
for i = 1, listlen - 1 do
local item = list[i]
local pos = item:find("=")
if pos then
o.supports[item:sub(1, pos - 1)] = item:sub(pos + 1)
else
o.supports[item] = true
end
end
end

--NAMES list
handlers["353"] = function(o, prefix, me, chanType, channel, names)
handlers["353"] = function(o, user, me, chanType, channel, names)
if o.track_users then
o.channels[channel] = o.channels[channel] or {users = {}, type = chanType}

local users = o.channels[channel].users
for nick in names:gmatch("(%S+)") do
local access, name = parseNick(nick)
local access, name = parseNick(o, nick)
users[name] = {access = access}
end
end
end

--end of NAMES
handlers["366"] = function(o, prefix, me, channel, msg)
handlers["366"] = function(o, user, me, channel, msg)
if o.track_users then
o:invoke("NameList", channel, msg)
end
end

--no topic
handlers["331"] = function(o, prefix, me, channel)
handlers["331"] = function(o, user, me, channel)
o:invoke("OnTopic", channel, nil)
end

--new topic
handlers["TOPIC"] = function(o, prefix, channel, topic)
handlers["TOPIC"] = function(o, user, channel, topic)
o:invoke("OnTopic", channel, topic)
end

handlers["332"] = function(o, prefix, me, channel, topic)
handlers["332"] = function(o, user, me, channel, topic)
o:invoke("OnTopic", channel, topic)
end

--topic creation info
handlers["333"] = function(o, prefix, me, channel, nick, time)
handlers["333"] = function(o, user, me, channel, nick, time)
o:invoke("OnTopicInfo", channel, nick, tonumber(time))
end

handlers["KICK"] = function(o, prefix, channel, kicked, reason)
o:invoke("OnKick", channel, kicked, parsePrefix(prefix), reason)
handlers["KICK"] = function(o, user, channel, kicked, reason)
o:invoke("OnKick", channel, kicked, user, reason)
end

--RPL_UMODEIS
--To answer a query about a client's own mode, RPL_UMODEIS is sent back
handlers["221"] = function(o, prefix, user, modes)
handlers["221"] = function(o, user, user, modes)
o:invoke("OnUserMode", modes)
end

--RPL_CHANNELMODEIS
--The result from common irc servers differs from that defined by the rfc
handlers["324"] = function(o, prefix, user, channel, modes)
handlers["324"] = function(o, user, user, channel, modes)
o:invoke("OnChannelMode", channel, modes)
end

handlers["MODE"] = function(o, prefix, target, modes, ...)
handlers["MODE"] = function(o, user, target, modes, ...)
if o.track_users and target ~= o.nick then
local add = true
local optList = {...}
updatePrefixModes(o)
for c in modes:gmatch(".") do
if c == "+" then add = true
elseif c == "-" then add = false
elseif c == "o" then
local user = table.remove(optList, 1)
o.channels[target].users[user].access.op = add
elseif c == "h" then
local user = table.remove(optList, 1)
o.channels[target].users[user].access.halfop = add
elseif c == "v" then
local user = table.remove(optList, 1)
o.channels[target].users[user].access.voice = add
elseif o.modeprefix[c] then
local nick = table.remove(optList, 1)
local access = o.channels[target].users[nick].access
access[o.modeprefix[c]] = add
if c == "o" then access.op = add
elseif c == "v" then access.voice = add
end
end
end
end
o:invoke("OnModeChange", parsePrefix(prefix), target, modes, ...)
o:invoke("OnModeChange", user, target, modes, ...)
end

handlers["ERROR"] = function(o, prefix, message)
handlers["ERROR"] = function(o, user, message)
o:invoke("OnDisconnect", message, true)
o:shutdown()
error(message, 3)
end

5 changes: 4 additions & 1 deletion init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function new(data)
nickGenerator = data.nickGenerator or defaultNickGenerator;
hooks = {};
track_users = true;
supports = {};
}
assert(checkNick(o.nick), "Erroneous nickname passed to irc.new")
return setmetatable(o, meta_preconnect)
Expand Down Expand Up @@ -182,10 +183,12 @@ end
local handlers = handlers

function meta:handle(prefix, cmd, params)
local user = parsePrefix(prefix)
local handler = handlers[cmd]
if handler then
return handler(self, prefix, unpack(params))
handler(self, user, unpack(params))
end
self:invoke("Do"..capitalize(cmd), user, unpack(params))
end

local whoisHandlers = {
Expand Down
52 changes: 40 additions & 12 deletions util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,51 @@ function parse(line)
return prefix, cmd, params
end

function parseNick(nick)
local access, name = nick:match("^([%+@]*)(.+)$")
return parseAccess(access or ""), name
function parseNick(conn, nick)
local access = {}
updatePrefixModes(conn)
local namestart = 1
for i = 1, #nick - 1 do
local c = nick:sub(i, i)
if conn.prefixmode[c] then
access[conn.prefixmode[c]] = true
else
namestart = i
break
end
end
access.op = access.o
access.voice = access.v
local name = nick:sub(namestart)
return access, name
end

function parsePrefix(prefix)
local user = {}
if prefix then
user.access, user.nick, user.username, user.host = prefix:match("^([%+@]*)(.+)!(.+)@(.+)$")
user.nick, user.username, user.host = prefix:match("^(.+)!(.+)@(.+)$")
end
user.access = parseAccess(user.access or "")
return user
end

function parseAccess(accessString)
local access = {op = false, halfop = false, voice = false}
for c in accessString:gmatch(".") do
if c == "@" then access.op = true
elseif c == "%" then access.halfop = true
elseif c == "+" then access.voice = true
function updatePrefixModes(conn)
if conn.prefixmode and conn.modeprefix then
return
end
conn.prefixmode = {}
conn.modeprefix = {}
if conn.supports.PREFIX then
local modes, prefixes = conn.supports.PREFIX:match("%(([^%)]*)%)(.*)")
for i = 1, #modes do
conn.prefixmode[prefixes:sub(i, i)] = modes:sub(i, i)
conn.modeprefix[ modes:sub(i, i)] = prefixes:sub(i, i)
end
else
conn.prefixmode['@'] = 'o'
conn.prefixmode['+'] = 'v'
conn.modeprefix['o'] = '@'
conn.modeprefix['v'] = '+'
end
return access
end

--mIRC markup scheme (de-facto standard)
Expand Down Expand Up @@ -134,3 +156,9 @@ function defaultNickGenerator(nick)
return nick
end

function capitalize(text)
-- Converts first character to upercase and the rest to lowercase.
-- "PING" -> "Ping" | "hello" -> "Hello" | "123" -> "123"
return text:sub(1, 1):upper()..text:sub(2):lower()
end