diff --git a/asyncoperations.lua b/asyncoperations.lua
index 9e9ba13..353b894 100644
--- a/asyncoperations.lua
+++ b/asyncoperations.lua
@@ -3,14 +3,19 @@ local assert = assert
local error = error
local select = select
local pairs = pairs
+local type = type
module "irc"
local meta = _META
function meta:send(msg, ...)
- if select("#", ...) > 0 then
- msg = msg:format(...)
+ if type(msg) == "table" then
+ msg = msg:toRFC1459()
+ else
+ if select("#", ...) > 0 then
+ msg = msg:format(...)
+ end
end
self:invoke("OnSend", msg)
@@ -34,28 +39,26 @@ end
function meta:sendChat(target, msg)
-- Split the message into segments if it includes newlines.
for line in msg:gmatch("([^\r\n]+)") do
- self:send("PRIVMSG %s :%s", verify(target, 3), line)
+ self:send(msgs.privmsg(verify(target, 3), line))
end
end
function meta:sendNotice(target, msg)
-- Split the message into segments if it includes newlines.
for line in msg:gmatch("([^\r\n]+)") do
- self:send("NOTICE %s :%s", verify(target, 3), line)
+ self:send(msgs.notice(verify(target, 3), line))
end
end
function meta:join(channel, key)
- if key then
- self:send("JOIN %s :%s", verify(channel, 3), verify(key, 3))
- else
- self:send("JOIN %s", verify(channel, 3))
- end
+ self:send(msgs.join(
+ verify(channel, 3),
+ key and verify(key, 3) or nil))
end
-function meta:part(channel)
+function meta:part(channel, reason)
channel = verify(channel, 3)
- self:send("PART %s", channel)
+ self:send(msgs.part(channel, reason))
if self.track_users then
self.channels[channel] = nil
end
@@ -85,5 +88,6 @@ function meta:setMode(t)
mode = table.concat{mode, "-", verify(rem, 3)}
end
- self:send("MODE %s %s", verify(target, 3), mode)
+ self:send(msgs.mode(verify(target, 3), mode))
end
+
diff --git a/doc/irc.luadoc b/doc/irc.luadoc
index 9f84bc9..879c75c 100644
--- a/doc/irc.luadoc
+++ b/doc/irc.luadoc
@@ -70,24 +70,24 @@ function irc:whois(nick)
-- @param channel Channel to query.
function irc:topic(channel)
---- Send a raw line of IRC to the server.
--- @param msg Line to be sent, excluding newline characters.
+--- Send a IRC message to the server.
+-- @param msg Message or raw line to send, excluding newline characters.
-- @param ... Format parameters for msg
, with string.format
semantics. [optional]
function irc:send(msg, ...)
--- Send a message to a channel or user.
-- @param target Nick or channel to send to.
--- @param message Message to send.
+-- @param message Message text.
function irc:sendChat(target, message)
--- Send a notice to a channel or user.
-- @param target Nick or channel to send to.
--- @param message Notice to send.
+-- @param message Notice text.
function irc:sendNotice(target, message)
--- Join a channel.
-- @param channel Channel to join.
--- @param key Channel password. [optional]
+-- @param key Channel key. [optional]
function irc:join(channel, key)
--- Leave a channel.
@@ -123,6 +123,16 @@ function irc:shutdown()
-- @name Connection
-- @class table
+--- Class representing an IRC message.
+--
+-- sender
+-- command
+-- args
+-- toRFC1459()
+--
+-- @name Message
+-- @class table
+
--- 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.
diff --git a/handlers.lua b/handlers.lua
index d56f2eb..38fd7f6 100644
--- a/handlers.lua
+++ b/handlers.lua
@@ -8,7 +8,7 @@ module "irc"
handlers = {}
handlers["PING"] = function(o, user, query)
- o:send("PONG :%s", query)
+ o:send(Message("PONG", {query}))
end
handlers["001"] = function(o, user, me)
@@ -77,7 +77,7 @@ end
local function needNewNick(o, user, target, badnick)
local newnick = o.nickGenerator(badnick)
- o:send("NICK %s", newnick)
+ o:send(msgs.nick(newnick))
end
-- ERR_ERRONEUSNICKNAME (Misspelt but remains for historical reasons)
diff --git a/init.lua b/init.lua
index ea30c2b..98f29eb 100644
--- a/init.lua
+++ b/init.lua
@@ -20,6 +20,7 @@ _META = meta
require "irc.util"
require "irc.asyncoperations"
require "irc.handlers"
+require "irc.messages"
local meta_preconnect = {}
function meta_preconnect.__index(o, k)
@@ -120,17 +121,17 @@ function meta_preconnect:connect(_host, _port)
self.socket = s
setmetatable(self, meta)
- self:send("CAP REQ multi-prefix")
+ self:send(Message("CAP", {"REQ", "multi-prefix"}))
self:invoke("PreRegister", self)
- self:send("CAP END")
+ self:send(Message("CAP", {"END"}))
if password then
- self:send("PASS %s", password)
+ self:send(Message("PASS", {password}))
end
- self:send("NICK %s", self.nick)
- self:send("USER %s 0 * :%s", self.username, self.realname)
+ self:send(msgs.nick(self.nick))
+ self:send(Message("USER", {self.username, "0", "*", self.realname}))
self.channels = {}
@@ -146,7 +147,7 @@ function meta:disconnect(message)
message = message or "Bye!"
self:invoke("OnDisconnect", message, false)
- self:send("QUIT :%s", message)
+ self:send(msgs.quit(message))
self:shutdown()
end
@@ -201,7 +202,7 @@ local whoisHandlers = {
}
function meta:whois(nick)
- self:send("WHOIS %s", nick)
+ self:send(msgs.whois(nick))
local result = {}
@@ -231,6 +232,6 @@ function meta:whois(nick)
end
function meta:topic(channel)
- self:send("TOPIC %s", channel)
+ self:send(msgs.topic(channel))
end
diff --git a/messages.lua b/messages.lua
new file mode 100644
index 0000000..00c0bd3
--- /dev/null
+++ b/messages.lua
@@ -0,0 +1,120 @@
+local assert = assert
+local setmetatable = setmetatable
+local unpack = unpack
+
+module "irc"
+
+msgs = {}
+
+local msg_meta = {}
+msg_meta.__index = msg_meta
+
+function Message(cmd, args)
+ return setmetatable({
+ command = cmd,
+ args = args or {},
+ }, msg_meta)
+end
+
+function msg_meta:toRFC1459()
+ s = self.command
+ argnum = #self.args
+ for i = 1, argnum do
+ local arg = self.args[i]
+ local startsWithColon = (arg:sub(1, 1) == ":")
+ local hasSpace = arg:find(" ")
+ if i == argnum and (hasSpace or startsWithColon) then
+ s = s.." :"
+ else
+ assert(not hasSpace and not startsWithColon,
+ "Message arguments can not be "
+ .."serialized to RFC1459 format")
+ s = s.." "
+ end
+ s = s..arg
+ end
+ return s
+end
+
+function msgs.privmsg(to, text)
+ return Message("PRIVMSG", {to, text})
+end
+
+function msgs.notice(to, text)
+ return Message("NOTICE", {to, text})
+end
+
+function msgs.action(to, text)
+ return Message("PRIVMSG", {to, ("\x01ACTION %s\x01"):format(text)})
+end
+
+function msgs.ctcp(command, to, args)
+ s = "\x01"..command
+ if args then
+ s = ' '..args
+ end
+ s = s..'\x01'
+ return Message("PRIVMSG", {to, s})
+end
+
+function msgs.kick(channel, target, reason)
+ return Message("KICK", {channel, target, reason})
+end
+
+function msgs.join(channel, key)
+ return Message("JOIN", {channel, key})
+end
+
+function msgs.part(channel, reason)
+ return Message("PART", {channel, reason})
+end
+
+function msgs.quit(reason)
+ return Message("QUIT", {reason})
+end
+
+function msgs.kill(target, reason)
+ return Message("KILL", {target, reason})
+end
+
+function msgs.kline(time, mask, reason, operreason)
+ local args = nil
+ if time then
+ args = {time, mask, reason..'|'..operreason}
+ else
+ args = {mask, reason..'|'..operreason}
+ end
+ return Message("KLINE", args)
+end
+
+function msgs.whois(nick, server)
+ local args = nil
+ if server then
+ args = {server, nick}
+ else
+ args = {nick}
+ end
+ return Message("WHOIS", args)
+end
+
+function msgs.topic(channel, text)
+ return Message("TOPIC", {channel, text})
+end
+
+function msgs.invite(channel, target)
+ return Message("INVITE", {channel, target})
+end
+
+function msgs.nick(nick)
+ return Message("NICK", {nick})
+end
+
+function msgs.mode(target, modes)
+ -- We have to split the modes parameter because the mode string and
+ -- each parameter are seperate arguments (The first command is incorrect)
+ -- MODE :+ov Nick1 Nick2
+ -- MODE +ov Nick1 Nick2
+ mt = split(modes)
+ return Message("MODE", {target, unpack(mt)})
+end
+
diff --git a/util.lua b/util.lua
index ceabd80..a16f7cd 100644
--- a/util.lua
+++ b/util.lua
@@ -162,3 +162,11 @@ function capitalize(text)
return text:sub(1, 1):upper()..text:sub(2):lower()
end
+function split(str, sep)
+ t = {}
+ for s in str:gmatch("%S+") do
+ table.insert(t, s)
+ end
+ return t
+end
+