aboutsummaryrefslogtreecommitdiffstats
path: root/src/irc.lua
diff options
context:
space:
mode:
authorjluehrs2 <jluehrs2@uiuc.edu>2007-08-26 22:07:38 -0500
committerjluehrs2 <jluehrs2@uiuc.edu>2007-08-26 22:07:38 -0500
commit86a0d68f9920cbcd382d8bb255639b022991def7 (patch)
treed9ecb2ec46ababa1654818d06d3015f5da746162 /src/irc.lua
parent27ea3f3876546997cfc838f6d605b8f4ca5adcd7 (diff)
downloadluairc-86a0d68f9920cbcd382d8bb255639b022991def7.tar.gz
luairc-86a0d68f9920cbcd382d8bb255639b022991def7.zip
add all of the current files
Diffstat (limited to 'src/irc.lua')
-rw-r--r--src/irc.lua842
1 files changed, 842 insertions, 0 deletions
diff --git a/src/irc.lua b/src/irc.lua
new file mode 100644
index 0000000..605e74d
--- /dev/null
+++ b/src/irc.lua
@@ -0,0 +1,842 @@
+-- initialization {{{
+local base = _G
+local constants = require 'irc.constants'
+local irc_debug = require 'irc.debug'
+local message = require 'irc.message'
+local misc = require 'irc.misc'
+local socket = require 'socket'
+local os = require 'os'
+local string = require 'string'
+local table = require 'table'
+-- }}}
+
+module 'irc'
+
+-- constants {{{
+_VERSION = 'LuaIRC 0.2'
+-- }}}
+
+-- classes {{{
+local Channel = base.require 'irc.channel'
+-- }}}
+
+-- local variables {{{
+local irc_sock = nil
+local rsockets = {}
+local wsockets = {}
+local rcallbacks = {}
+local wcallbacks = {}
+local icallbacks = {
+ whois = {},
+ serverversion = {},
+ servertime = {},
+ ctcp_ping = {},
+ ctcp_time = {},
+ ctcp_version = {},
+}
+local requestinfo = {whois = {}}
+local handlers = {}
+local ctcp_handlers = {}
+local serverinfo = {}
+-- }}}
+
+-- defaults {{{
+TIMEOUT = 60 -- connection timeout
+NETWORK = "localhost" -- default network
+PORT = 6667 -- default port
+NICK = "luabot" -- default nick
+USERNAME = "LuaIRC" -- default username
+REALNAME = "LuaIRC" -- default realname
+DEBUG = false -- whether we want extra debug information
+OUTFILE = nil -- file to send debug output to - nil is stdout
+-- }}}
+
+-- private functions {{{
+-- main_loop_iter {{{
+local function main_loop_iter()
+ if #rsockets == 0 and #wsockets == 0 then return false end
+ local rready, wready, err = socket.select(rsockets, wsockets)
+ if err then irc_debug.err(err); return false; end
+
+ for _, sock in base.ipairs(rready) do
+ local cb = socket.protect(rcallbacks[sock])
+ local ret, err = cb(sock)
+ if not ret then
+ irc_debug.warn("socket error: " .. err)
+ _unregister_socket(sock, 'r')
+ end
+ end
+
+ for _, sock in base.ipairs(wready) do
+ local cb = socket.protect(wcallbacks[sock])
+ local ret, err = cb(sock)
+ if not ret then
+ irc_debug.warn("socket error: " .. err)
+ _unregister_socket(sock, 'w')
+ end
+ end
+
+ return true
+end
+-- }}}
+
+-- begin_main_loop {{{
+local function begin_main_loop()
+ while main_loop_iter() do end
+end
+-- }}}
+
+-- incoming_message {{{
+local function incoming_message(sock)
+ local raw_msg = socket.try(sock:receive())
+ irc_debug.message("RECV", raw_msg)
+ local msg = message.parse(raw_msg)
+ misc.try_call_warn("Unhandled server message: " .. msg.command,
+ handlers["on_" .. msg.command:lower()],
+ (misc.parse_user(msg.from)), base.unpack(msg.args))
+ return true
+end
+-- }}}
+-- }}}
+
+-- internal message handlers {{{
+-- command handlers {{{
+-- on_nick {{{
+function handlers.on_nick(from, new_nick)
+ for chan in channels() do
+ chan:change_nick(from, new_nick)
+ end
+ misc.try_call(on_nick_change, new_nick, from)
+end
+-- }}}
+
+-- on_join {{{
+function handlers.on_join(from, chan)
+ base.assert(serverinfo.channels[chan],
+ "Received join message for unknown channel: " .. chan)
+ if serverinfo.channels[chan].join_complete then
+ serverinfo.channels[chan]:add_user(from)
+ misc.try_call(on_join, serverinfo.channels[chan], from)
+ end
+end
+-- }}}
+
+-- on_part {{{
+function handlers.on_part(from, chan, part_msg)
+ -- don't assert on chan here, since we get part messages for ourselves
+ -- after we remove the channel from the channel list
+ if not serverinfo.channels[chan] then return end
+ if serverinfo.channels[chan].join_complete then
+ serverinfo.channels[chan]:remove_user(from)
+ misc.try_call(on_part, serverinfo.channels[chan], from, part_msg)
+ end
+end
+-- }}}
+
+-- on_mode {{{
+function handlers.on_mode(from, to, mode_string, ...)
+ local dir = mode_string:sub(1, 1)
+ mode_string = mode_string:sub(2)
+ local args = {...}
+
+ if to:sub(1, 1) == "#" then
+ -- handle channel mode requests {{{
+ base.assert(serverinfo.channels[to],
+ "Received mode change for unknown channel: " .. to)
+ local chan = serverinfo.channels[to]
+ local ind = 1
+ for i = 1, mode_string:len() do
+ local mode = mode_string:sub(i, i)
+ local target = args[ind]
+ -- channel modes other than op/voice will be implemented as
+ -- information request commands
+ if mode == "o" then -- channel op {{{
+ chan:change_status(target, dir == "+", "o")
+ misc.try_call(({["+"] = on_op, ["-"] = on_deop})[dir],
+ chan, from, target)
+ ind = ind + 1
+ -- }}}
+ elseif mode == "v" then -- voice {{{
+ chan:change_status(target, dir == "+", "v")
+ misc.try_call(({["+"] = on_voice, ["-"] = on_devoice})[dir],
+ chan, from, target)
+ ind = ind + 1
+ -- }}}
+ end
+ end
+ -- }}}
+ elseif from == to then
+ -- handle user mode requests {{{
+ -- TODO: make users more easily accessible so this is actually
+ -- reasonably possible
+ for i = 1, mode_string:len() do
+ local mode = mode_string:sub(i, i)
+ if mode == "i" then -- invisible {{{
+ -- }}}
+ elseif mode == "s" then -- server messages {{{
+ -- }}}
+ elseif mode == "w" then -- wallops messages {{{
+ -- }}}
+ elseif mode == "o" then -- ircop {{{
+ -- }}}
+ end
+ end
+ -- }}}
+ end
+end
+-- }}}
+
+-- on_topic {{{
+function handlers.on_topic(from, chan, new_topic)
+ base.assert(serverinfo.channels[chan],
+ "Received topic message for unknown channel: " .. chan)
+ serverinfo.channels[chan]._topic.text = new_topic
+ serverinfo.channels[chan]._topic.user = (misc.parse_user(from))
+ serverinfo.channels[chan]._topic.time = os.time()
+ if serverinfo.channels[chan].join_complete then
+ misc.try_call(on_topic_change, serverinfo.channels[chan])
+ end
+end
+-- }}}
+
+-- on_invite {{{
+function handlers.on_invite(from, to, chan)
+ misc.try_call(on_invite, from, chan)
+end
+-- }}}
+
+-- on_kick {{{
+function handlers.on_kick(from, chan, to)
+ base.assert(serverinfo.channels[chan],
+ "Received kick message for unknown channel: " .. chan)
+ if serverinfo.channels[chan].join_complete then
+ serverinfo.channels[chan]:remove_user(to)
+ misc.try_call(on_kick, serverinfo.channels[chan], to, from)
+ end
+end
+-- }}}
+
+-- on_privmsg {{{
+function handlers.on_privmsg(from, to, msg)
+ local msgs = ctcp.ctcp_split(msg, true)
+ for _, v in base.ipairs(msgs) do
+ if base.type(v) == "string" then
+ -- normal message {{{
+ if to:sub(1, 1) == "#" then
+ base.assert(serverinfo.channels[to],
+ "Received channel msg from unknown channel: " .. to)
+ misc.try_call(on_channel_msg, serverinfo.channels[to], from, v)
+ else
+ misc.try_call(on_private_msg, from, v)
+ end
+ -- }}}
+ elseif base.type(v) == "table" then
+ -- ctcp message {{{
+ local words = misc.split(v[1])
+ local received_command = words[1]
+ local cb = "on_" .. received_command:lower()
+ table.remove(words, 1)
+ -- not using try_call here because the ctcp specification requires
+ -- an error response to nonexistant commands
+ if base.type(ctcp_handlers[cb]) == "function" then
+ ctcp_handlers[cb](from, to, table.concat(words, " "))
+ else
+ notice(from, {"ERRMSG Unknown query: " .. received_command})
+ end
+ -- }}}
+ end
+ end
+end
+-- }}}
+
+-- on_notice {{{
+function handlers.on_notice(from, to, msg)
+ local msgs = ctcp.ctcp_split(msg, true)
+ for _, v in base.ipairs(msgs) do
+ if base.type(v) == "string" then
+ -- normal message {{{
+ if to:sub(1, 1) == "#" then
+ base.assert(serverinfo.channels[to],
+ "Received channel msg from unknown channel: " .. to)
+ misc.try_call(on_channel_notice, serverinfo.channels[to],
+ from, v)
+ else
+ misc.try_call(on_private_notice, from, v)
+ end
+ -- }}}
+ elseif base.type(v) == "table" then
+ -- ctcp message {{{
+ local words = misc.split(v[1])
+ local command = words[1]:lower()
+ table.remove(words, 1)
+ misc.try_call_warn("Unknown CTCP message: " .. command,
+ ctcp_handlers["on_rpl_"..command], from, to,
+ table.concat(words, ' '))
+ -- }}}
+ end
+ end
+end
+-- }}}
+
+-- on_quit {{{
+function handlers.on_quit(from, quit_msg)
+ for name, chan in base.pairs(serverinfo.channels) do
+ chan:remove_user(from)
+ end
+ misc.try_call(on_quit, from, quit_msg)
+end
+-- }}}
+
+-- on_ping {{{
+-- respond to server pings to make sure it knows we are alive
+function handlers.on_ping(from, respond_to)
+ send("PONG", respond_to)
+end
+-- }}}
+-- }}}
+
+-- server replies {{{
+-- on_rpl_topic {{{
+-- catch topic changes
+function handlers.on_rpl_topic(from, chan, topic)
+ base.assert(serverinfo.channels[chan],
+ "Received topic information about unknown channel: " .. chan)
+ serverinfo.channels[chan]._topic.text = topic
+end
+-- }}}
+
+-- on_rpl_notopic {{{
+function handlers.on_rpl_notopic(from, chan)
+ base.assert(serverinfo.channels[chan],
+ "Received topic information about unknown channel: " .. chan)
+ serverinfo.channels[chan]._topic.text = ""
+end
+-- }}}
+
+-- on_rpl_topicdate {{{
+-- "topic was set by <user> at <time>"
+function handlers.on_rpl_topicdate(from, chan, user, time)
+ base.assert(serverinfo.channels[chan],
+ "Received topic information about unknown channel: " .. chan)
+ serverinfo.channels[chan]._topic.user = user
+ serverinfo.channels[chan]._topic.time = base.tonumber(time)
+end
+-- }}}
+
+-- on_rpl_namreply {{{
+-- handles a NAMES reply
+function handlers.on_rpl_namreply(from, chanmode, chan, userlist)
+ base.assert(serverinfo.channels[chan],
+ "Received user information about unknown channel: " .. chan)
+ serverinfo.channels[chan]._chanmode = constants.chanmodes[chanmode]
+ local users = misc.split(userlist)
+ for k,v in base.ipairs(users) do
+ if v:sub(1, 1) == "@" or v:sub(1, 1) == "+" then
+ local nick = v:sub(2)
+ serverinfo.channels[chan]:add_user(nick, v:sub(1, 1))
+ else
+ serverinfo.channels[chan]:add_user(v)
+ end
+ end
+end
+-- }}}
+
+-- on_rpl_endofnames {{{
+-- when we get this message, the channel join has completed, so call the
+-- external cb
+function handlers.on_rpl_endofnames(from, chan)
+ base.assert(serverinfo.channels[chan],
+ "Received user information about unknown channel: " .. chan)
+ if not serverinfo.channels[chan].join_complete then
+ misc.try_call(on_me_join, serverinfo.channels[chan])
+ serverinfo.channels[chan].join_complete = true
+ end
+end
+-- }}}
+
+-- on_rpl_welcome {{{
+function handlers.on_rpl_welcome(from)
+ serverinfo = {
+ connected = false,
+ connecting = true,
+ channels = {}
+ }
+end
+-- }}}
+
+-- on_rpl_yourhost {{{
+function handlers.on_rpl_yourhost(from, msg)
+ serverinfo.host = from
+end
+-- }}}
+
+-- on_rpl_motdstart {{{
+function handlers.on_rpl_motdstart(from)
+ serverinfo.motd = ""
+end
+-- }}}
+
+-- on_rpl_motd {{{
+function handlers.on_rpl_motd(from, motd)
+ serverinfo.motd = (serverinfo.motd or "") .. motd .. "\n"
+end
+-- }}}
+
+-- on_rpl_endofmotd {{{
+function handlers.on_rpl_endofmotd(from)
+ if not serverinfo.connected then
+ serverinfo.connected = true
+ serverinfo.connecting = false
+ misc.try_call(on_connect)
+ end
+end
+-- }}}
+
+-- on_rpl_whoisuser {{{
+function handlers.on_rpl_whoisuser(from, nick, user, host, star, realname)
+ nick = nick:lower()
+ requestinfo.whois[nick].user = user
+ requestinfo.whois[nick].host = host
+ requestinfo.whois[nick].realname = realname
+end
+-- }}}
+
+-- on_rpl_whoischannels {{{
+function handlers.on_rpl_whoischannels(from, nick, channel_list)
+ nick = nick:lower()
+ if not requestinfo.whois[nick].channels then
+ requestinfo.whois[nick].channels = {}
+ end
+ for _, channel in base.ipairs(misc.split(channel_list)) do
+ table.insert(requestinfo.whois[nick].channels, channel)
+ end
+end
+-- }}}
+
+-- on_rpl_whoisserver {{{
+function handlers.on_rpl_whoisserver(from, nick, server, serverinfo)
+ nick = nick:lower()
+ requestinfo.whois[nick].server = server
+ requestinfo.whois[nick].serverinfo = serverinfo
+end
+-- }}}
+
+-- on_rpl_away {{{
+function handlers.on_rpl_away(from, nick, away_msg)
+ nick = nick:lower()
+ if requestinfo.whois[nick] then
+ requestinfo.whois[nick].away_msg = away_msg
+ end
+end
+-- }}}
+
+-- on_rpl_whoisoperator {{{
+function handlers.on_rpl_whoisoperator(from, nick)
+ requestinfo.whois[nick:lower()].is_oper = true
+end
+-- }}}
+
+-- on_rpl_whoisidle {{{
+function handlers.on_rpl_whoisidle(from, nick, idle_seconds)
+ requestinfo.whois[nick:lower()].idle_time = idle_seconds
+end
+-- }}}
+
+-- on_rpl_endofwhois {{{
+function handlers.on_rpl_endofwhois(from, nick)
+ nick = nick:lower()
+ local cb = table.remove(icallbacks.whois[nick], 1)
+ cb(requestinfo.whois[nick])
+ requestinfo.whois[nick] = nil
+ if #icallbacks.whois[nick] > 0 then send("WHOIS", nick)
+ else icallbacks.whois[nick] = nil
+ end
+end
+-- }}}
+
+-- on_rpl_version {{{
+function handlers.on_rpl_version(from, version, server, comments)
+ local cb = table.remove(icallbacks.serverversion[server], 1)
+ cb({version = version, server = server, comments = comments})
+ if #icallbacks.serverversion[server] > 0 then send("VERSION", server)
+ else icallbacks.serverversion[server] = nil
+ end
+end
+-- }}}
+
+-- on_rpl_time {{{
+function on_rpl_time(from, server, time)
+ local cb = table.remove(icallbacks.servertime[server], 1)
+ cb({time = time, server = server})
+ if #icallbacks.servertime[server] > 0 then send("TIME", server)
+ else icallbacks.servertime[server] = nil
+ end
+end
+-- }}}
+-- }}}
+
+-- ctcp handlers {{{
+-- requests {{{
+-- on_action {{{
+function ctcp_handlers.on_action(from, to, message)
+ if to:sub(1, 1) == "#" then
+ base.assert(serverinfo.channels[to],
+ "Received channel msg from unknown channel: " .. to)
+ misc.try_call(on_channel_act, serverinfo.channels[to], from, message)
+ else
+ misc.try_call(on_private_act, from, message)
+ end
+end
+-- }}}
+
+-- on_dcc {{{
+function ctcp_handlers.on_dcc(from, to, message)
+ local type, argument, address, port, size = base.unpack(misc.split(message, " ", nil, '"', '"'))
+ if type == "SEND" then
+ if misc.try_call(on_dcc, from, to, argument, address, port, size) then
+ dcc.accept(argument, address, port, size)
+ end
+ elseif type == "CHAT" then
+ -- TODO: implement this? do people ever use this?
+ end
+end
+-- }}}
+
+-- on_version {{{
+function ctcp_handlers.on_version(from, to)
+ notice(from, {"VERSION " .. _VERSION .. " running under " .. base._VERSION .. " with " .. socket._VERSION})
+end
+-- }}}
+
+-- on_errmsg {{{
+function ctcp_handlers.on_errmsg(from, to, message)
+ notice(from, {"ERRMSG " .. message .. "No error has occurred"})
+end
+-- }}}
+
+-- on_ping {{{
+function ctcp_handlers.on_ping(from, to, timestamp)
+ notice(from, {"PING " .. timestamp})
+end
+-- }}}
+
+-- on_time {{{
+function ctcp_handlers.on_time(from, to)
+ notice(from, {"TIME " .. os.date()})
+end
+-- }}}
+-- }}}
+
+-- responses {{{
+-- on_rpl_action {{{
+-- actions are handled the same, notice or not
+ctcp_handlers.on_rpl_action = ctcp_handlers.on_action
+-- }}}
+
+-- on_rpl_version {{{
+function ctcp_handlers.on_rpl_version(from, to, version)
+ local cb = table.remove(icallbacks.ctcp_version[from], 1)
+ cb({version = version, nick = from})
+ if #icallbacks.ctcp_version[from] > 0 then say(from, {"VERSION"})
+ else icallbacks.ctcp_version[from] = nil
+ end
+end
+-- }}}
+
+-- on_rpl_errmsg {{{
+function ctcp_handlers.on_rpl_errmsg(from, to, message)
+ try_call(on_ctcp_error, from, to, message)
+end
+-- }}}
+
+-- on_rpl_ping {{{
+function ctcp_handlers.on_rpl_ping(from, to, timestamp)
+ local cb = table.remove(icallbacks.ctcp_ping[from], 1)
+ cb({time = os.time() - timestamp, nick = from})
+ if #icallbacks.ctcp_ping[from] > 0 then say(from, {"PING " .. os.time()})
+ else icallbacks.ctcp_ping[from] = nil
+ end
+end
+-- }}}
+
+-- on_rpl_time {{{
+function ctcp_handlers.on_rpl_time(from, to, time)
+ local cb = table.remove(icallbacks.ctcp_time[from], 1)
+ cb({time = time, nick = from})
+ if #icallbacks.ctcp_time[from] > 0 then say(from, {"TIME"})
+ else icallbacks.ctcp_time[from] = nil
+ end
+end
+-- }}}
+-- }}}
+-- }}}
+-- }}}
+
+-- module functions {{{
+-- socket handling functions {{{
+-- _register_socket() - register a socket to listen on {{{
+function _register_socket(sock, mode, cb)
+ local socks, cbs
+ if mode == 'r' then
+ socks = rsockets
+ cbs = rcallbacks
+ else
+ socks = wsockets
+ cbs = wcallbacks
+ end
+ base.assert(not cbs[sock], "socket already registered")
+ table.insert(socks, sock)
+ cbs[sock] = cb
+end
+-- }}}
+
+-- _unregister_socket() - remove a previously registered socket {{{
+function _unregister_socket(sock, mode)
+ local socks, cbs
+ if mode == 'r' then
+ socks = rsockets
+ cbs = rcallbacks
+ else
+ socks = wsockets
+ cbs = wcallbacks
+ end
+ for i, v in base.ipairs(socks) do
+ if v == sock then table.remove(socks, i); break; end
+ end
+ cbs[sock] = nil
+end
+-- }}}
+-- }}}
+-- }}}
+
+-- public functions {{{
+-- server commands {{{
+-- connect() - start a connection to the irc server {{{
+-- args: network - address of the irc network to connect to
+-- port - port to connect to
+-- pass - irc server password (if required)
+-- nick - nickname to connect as
+-- username - username to connect with
+-- realname - realname to connect with
+-- timeout - amount of time in seconds to wait before dropping an idle
+-- connection
+-- notes: this function uses a table and named arguments. defaults are specified
+-- by the capitalized versions of the arguments at the top of this file.
+-- all args are optional.
+function connect(args)
+ local network = args.network or NETWORK
+ local port = args.port or PORT
+ local nick = args.nick or NICK
+ local username = args.username or USERNAME
+ local realname = args.realname or REALNAME
+ local timeout = args.timeout or TIMEOUT
+ serverinfo.connecting = true
+ if OUTFILE then irc_debug.set_output(OUTFILE) end
+ if DEBUG then irc_debug.enable() end
+ irc_sock = base.assert(socket.connect(network, port))
+ irc_sock:settimeout(timeout)
+ _register_socket(irc_sock, 'r', incoming_message)
+ if args.pass then send("PASS", args.pass) end
+ send("NICK", nick)
+ send("USER", username, (irc_sock:getsockname()), network, realname)
+ begin_main_loop()
+end
+-- }}}
+
+-- quit() - close the connection to the irc server {{{
+-- args: message - quit message (optional)
+function quit(message)
+ message = message or "Leaving"
+ send("QUIT", message)
+ serverinfo.connected = false
+end
+-- }}}
+
+-- join() - join a channel {{{
+-- args: channel - channel to join (required)
+function join(channel)
+ if not channel then return end
+ serverinfo.channels[channel] = Channel.new(channel)
+ send("JOIN", channel)
+end
+-- }}}
+
+-- part() - leave a channel {{{
+-- args: channel - channel to leave (required)
+function part(channel)
+ if not channel then return end
+ serverinfo.channels[channel] = nil
+ send("PART", channel)
+end
+-- }}}
+
+-- say() - send a message to a user or channel {{{
+-- args: name - user or channel to send the message to
+-- message - message to send
+function say(name, message)
+ if not name then return end
+ message = message or ""
+ send("PRIVMSG", name, message)
+end
+-- }}}
+
+-- notice() - send a notice to a user or channel {{{
+-- args: name - user or channel to send the notice to
+-- message - message to send
+function notice(name, message)
+ if not name then return end
+ message = message or ""
+ send("NOTICE", name, message)
+end
+-- }}}
+
+-- act() - perform a /me action {{{
+-- args: name - user or channel to send the action to
+-- action - action to send
+function act(name, action)
+ if not name then return end
+ action = action or ""
+ send("PRIVMSG", name, {"ACTION", action})
+end
+-- }}}
+-- }}}
+
+-- information requests {{{
+-- server_version {{{
+function server_version(cb, server)
+ -- apparently the optional server parameter isn't supported?
+ --server = server or serverinfo.host
+ server = serverinfo.host
+ if not icallbacks.serverversion[server] then
+ icallbacks.serverversion[server] = {cb}
+ send("VERSION", server)
+ else
+ table.insert(icallbacks.serverversion[server], cb)
+ end
+end
+-- }}}
+
+-- whois {{{
+-- TODO: allow server parameter (to get user idle time)
+function whois(cb, nick)
+ nick = nick:lower()
+ requestinfo.whois[nick] = {nick = nick}
+ if not icallbacks.whois[nick] then
+ icallbacks.whois[nick] = {cb}
+ send("WHOIS", nick)
+ else
+ table.insert(icallbacks.whois[nick], cb)
+ end
+end
+-- }}}
+
+-- server_time {{{
+function server_time(cb, server)
+ -- apparently the optional server parameter isn't supported?
+ --server = server or serverinfo.host
+ server = serverinfo.host
+ if not icallbacks.servertime[server] then
+ icallbacks.servertime[server] = {cb}
+ send("TIME", server)
+ else
+ table.insert(icallbacks.servertime[server], cb)
+ end
+end
+-- }}}
+
+-- trace {{{
+--function trace(cb, server)
+-- send("WHOWAS", "ekiM")
+--end
+-- }}}
+-- }}}
+
+-- ctcp commands {{{
+-- ctcp_ping() - send a CTCP ping request {{{
+function ctcp_ping(cb, nick)
+ nick = nick:lower()
+ if not icallbacks.ctcp_ping[nick] then
+ icallbacks.ctcp_ping[nick] = {cb}
+ say(nick, {"PING " .. os.time()})
+ else
+ table.insert(icallbacks.ctcp_ping[nick], cb)
+ end
+end
+-- }}}
+
+-- ctcp_time() - send a localtime request {{{
+function ctcp_time(cb, nick)
+ nick = nick:lower()
+ if not icallbacks.ctcp_time[nick] then
+ icallbacks.ctcp_time[nick] = {cb}
+ say(nick, {"TIME"})
+ else
+ table.insert(icallbacks.ctcp_time[nick], cb)
+ end
+end
+-- }}}
+
+-- ctcp_version() - send a client version request {{{
+function ctcp_version(cb, nick)
+ nick = nick:lower()
+ if not icallbacks.ctcp_version[nick] then
+ icallbacks.ctcp_version[nick] = {cb}
+ say(nick, {"VERSION"})
+ else
+ table.insert(icallbacks.ctcp_version[nick], cb)
+ end
+end
+-- }}}
+-- }}}
+
+-- misc functions {{{
+-- send() - send a raw irc command {{{
+-- send takes a command and a variable number of arguments
+-- if the argument is a string, it is sent literally
+-- if the argument is a table, it is CTCP quoted
+-- the last argument is preceded by a :
+function send(command, ...)
+ if not serverinfo.connected and not serverinfo.connecting then return end
+ local message = command
+ for i, v in base.ipairs({...}) do
+ local arg
+ -- passing a table in as an argument means to treat that table as a
+ -- CTCP command, so quote it appropriately
+ if base.type(v) == "string" then
+ arg = v
+ elseif base.type(v) == "table" then
+ arg = ctcp.ctcp_quote(table.concat(v, " "))
+ end
+ if i == #{...} then
+ arg = ":" .. arg
+ end
+ message = message .. " " .. arg
+ end
+ message = ctcp.low_quote(message)
+ -- we just truncate for now. -2 to account for the \r\n
+ message = message:sub(1, constants.IRC_MAX_MSG - 2)
+ irc_debug.message("SEND", message)
+ irc_sock:send(message .. "\r\n")
+end
+-- }}}
+
+-- get_ip() - get the local ip address for the server connection {{{
+function get_ip()
+ return (irc_sock:getsockname())
+end
+-- }}}
+
+-- channels() - iterate over currently joined channels {{{
+function channels()
+ return function(state, arg)
+ return misc.value_iter(state, arg,
+ function(v)
+ return v.join_complete
+ end)
+ end,
+ serverinfo.channels,
+ nil
+end
+-- }}}
+-- }}}
+-- }}}