local handlers = {}
local ctcp_handlers = {}
local serverinfo = {}
+local ip = nil
-- }}}
-- defaults {{{
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
+ 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)
+ irc_debug._warn("socket error: " .. err)
_unregister_socket(sock, 'r')
end
end
local cb = socket.protect(wcallbacks[sock])
local ret, err = cb(sock)
if not ret then
- irc_debug.warn("socket error: " .. err)
+ irc_debug._warn("socket error: " .. err)
_unregister_socket(sock, 'w')
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))
+ 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
-- }}}
-- on_nick {{{
function handlers.on_nick(from, new_nick)
for chan in channels() do
- chan:change_nick(from, new_nick)
+ chan:_change_nick(from, new_nick)
end
- misc.try_call(on_nick_change, new_nick, from)
+ misc._try_call(on_nick_change, new_nick, from)
end
-- }}}
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)
+ serverinfo.channels[chan]:_add_user(from)
+ misc._try_call(on_join, serverinfo.channels[chan], from)
end
end
-- }}}
-- 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)
+ serverinfo.channels[chan]:_remove_user(from)
+ misc._try_call(on_part, serverinfo.channels[chan], from, part_msg)
end
end
-- }}}
-- 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)
+ 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)
+ chan:_change_status(target, dir == "+", "v")
+ misc._try_call(({["+"] = on_voice, ["-"] = on_devoice})[dir],
+ chan, from, target)
ind = ind + 1
-- }}}
end
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])
+ 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)
+ misc._try_call(on_invite, from, chan)
end
-- }}}
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)
+ 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)
+ 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)
+ misc._try_call(on_channel_msg, serverinfo.channels[to], from, v)
else
- misc.try_call(on_private_msg, from, v)
+ misc._try_call(on_private_msg, from, v)
end
-- }}}
elseif base.type(v) == "table" then
-- ctcp message {{{
- local words = misc.split(v[1])
+ local words = misc._split(v[1])
local received_command = words[1]
local cb = "on_" .. received_command:lower()
table.remove(words, 1)
-- on_notice {{{
function handlers.on_notice(from, to, msg)
- local msgs = ctcp.ctcp_split(msg, true)
+ 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)
+ misc._try_call(on_channel_notice, serverinfo.channels[to],
+ from, v)
else
- misc.try_call(on_private_notice, from, v)
+ misc._try_call(on_private_notice, from, v)
end
-- }}}
elseif base.type(v) == "table" then
-- ctcp message {{{
- local words = misc.split(v[1])
+ 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, ' '))
+ misc._try_call_warn("Unknown CTCP message: " .. command,
+ ctcp_handlers["on_rpl_"..command], from, to,
+ table.concat(words, ' '))
-- }}}
end
end
-- on_quit {{{
function handlers.on_quit(from, quit_msg)
for name, chan in base.pairs(serverinfo.channels) do
- chan:remove_user(from)
+ chan:_remove_user(from)
end
- misc.try_call(on_quit, from, quit_msg)
+ misc._try_call(on_quit, from, quit_msg)
end
-- }}}
base.assert(serverinfo.channels[chan],
"Received user information about unknown channel: " .. chan)
serverinfo.channels[chan]._chanmode = constants.chanmodes[chanmode]
- local users = misc.split(userlist)
+ 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))
+ serverinfo.channels[chan]:_add_user(nick, v:sub(1, 1))
else
- serverinfo.channels[chan]:add_user(v)
+ serverinfo.channels[chan]:_add_user(v)
end
end
end
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])
+ misc._try_call(on_me_join, serverinfo.channels[chan])
serverinfo.channels[chan].join_complete = true
end
end
if not serverinfo.connected then
serverinfo.connected = true
serverinfo.connecting = false
- misc.try_call(on_connect)
+ misc._try_call(on_connect)
end
end
-- }}}
if not requestinfo.whois[nick].channels then
requestinfo.whois[nick].channels = {}
end
- for _, channel in base.ipairs(misc.split(channel_list)) do
+ for _, channel in base.ipairs(misc._split(channel_list)) do
table.insert(requestinfo.whois[nick].channels, channel)
end
end
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)
+ misc._try_call(on_channel_act, serverinfo.channels[to], from, message)
else
- misc.try_call(on_private_act, from, message)
+ misc._try_call(on_private_act, from, message)
end
end
-- }}}
-- on_dcc {{{
+-- TODO: can we not have this handler be registered unless the dcc module is
+-- loaded?
function ctcp_handlers.on_dcc(from, to, message)
- local type, argument, address, port, size = base.unpack(misc.split(message, " ", nil, '"', '"'))
+ 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)
+ if misc._try_call(on_dcc, from, to, argument, address, port, size) then
+ dcc._accept(argument, address, port)
end
elseif type == "CHAT" then
-- TODO: implement this? do people ever use this?
-- module functions {{{
-- socket handling functions {{{
--- _register_socket() - register a socket to listen on {{{
+-- _register_socket {{{
+--
+-- Register a socket to listen on.
+-- @param sock LuaSocket socket object
+-- @param mode 'r' if the socket is for reading, 'w' if for writing
+-- @param cb Callback to call when the socket is ready for reading/writing.
+-- It will be called with the socket as the single argument.
function _register_socket(sock, mode, cb)
local socks, cbs
if mode == 'r' then
end
-- }}}
--- _unregister_socket() - remove a previously registered socket {{{
+-- _unregister_socket {{{
+--
+-- Remove a previously registered socket.
+-- @param sock Socket to unregister
+-- @param mode 'r' to unregister it for reading, 'w' for writing
function _unregister_socket(sock, mode)
local socks, cbs
if mode == 'r' then
-- 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.
+-- connect {{{
+---
+-- Start a connection to the irc server.
+-- @param args Table of named arguments containing connection parameters.
+-- Defaults are the all-caps versions of these parameters given
+-- at the top of the file, and are overridable by setting them
+-- as well, i.e. <pre>irc.NETWORK = irc.freenode.net</pre>
+-- Possible options are:
+-- <ul>
+-- <li><i>network:</i> address of the irc network to connect to
+-- (default: 'localhost')</li>
+-- <li><i>port:</i> port to connect to
+-- (default: '6667')</li>
+-- <li><i>pass:</i> irc server password
+-- (default: don't send)</li>
+-- <li><i>nick:</i> nickname to connect as
+-- (default: 'luabot')</li>
+-- <li><i>username:</i> username to connect with
+-- (default: 'LuaIRC')</li>
+-- <li><i>realname:</i> realname to connect with
+-- (default: 'LuaIRC')</li>
+-- <li><i>timeout:</i> amount of time in seconds to wait before
+-- dropping an idle connection
+-- (default: '60')</li>
+-- </ul>
function connect(args)
local network = args.network or NETWORK
local port = args.port or PORT
_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)
+ send("USER", username, get_ip(), network, realname)
begin_main_loop()
end
-- }}}
--- quit() - close the connection to the irc server {{{
--- args: message - quit message (optional)
+-- quit {{{
+---
+-- Close the connection to the irc server.
+-- @param message Quit message (optional, defaults to 'Leaving')
function quit(message)
message = message or "Leaving"
send("QUIT", message)
end
-- }}}
--- join() - join a channel {{{
--- args: channel - channel to join (required)
+-- join {{{
+---
+-- Join a channel.
+-- @param channel Channel to join
function join(channel)
if not channel then return end
serverinfo.channels[channel] = Channel.new(channel)
end
-- }}}
--- part() - leave a channel {{{
--- args: channel - channel to leave (required)
+-- part {{{
+---
+-- Leave a channel.
+-- @param channel Channel to leave
function part(channel)
if not channel then return end
serverinfo.channels[channel] = nil
end
-- }}}
--- say() - send a message to a user or channel {{{
--- args: name - user or channel to send the message to
--- message - message to send
+-- say {{{
+---
+-- Send a message to a user or channel.
+-- @param name User or channel to send the message to
+-- @param message Message to send
function say(name, message)
if not name then return end
message = message or ""
end
-- }}}
--- notice() - send a notice to a user or channel {{{
--- args: name - user or channel to send the notice to
--- message - message to send
+-- notice {{{
+---
+-- Send a notice to a user or channel.
+-- @param name User or channel to send the notice to
+-- @param message Message to send
function notice(name, message)
if not name then return end
message = message or ""
end
-- }}}
--- act() - perform a /me action {{{
--- args: name - user or channel to send the action to
--- action - action to send
+-- act {{{
+---
+-- Perform a /me action.
+-- @param name User or channel to send the action to
+-- @param action Action to send
function act(name, action)
if not name then return end
action = action or ""
-- information requests {{{
-- server_version {{{
+---
+-- Request the version of the IRC server you are currently connected to.
+-- @param cb Callback to call when the information is available. The single
+-- table parameter to this callback will contain the fields:
+-- <ul>
+-- <li><i>server:</i> the server which responded to the request</li>
+-- <li><i>version:</i> the server version</li>
+-- <li><i>comments:</i> other data provided by the server</li>
+-- </ul>
function server_version(cb)
-- apparently the optional server parameter isn't supported for servers
-- which you are not directly connected to (freenode specific?)
-- whois {{{
-- TODO: allow server parameter (to get user idle time)
+---
+-- Request WHOIS information about a given user.
+-- @param cb Callback to call when the information is available. The single
+-- table parameter to this callback may contain any or all of the
+-- fields:
+-- <ul>
+-- <li><i>nick:</i> the nick that was passed to this function
+-- (this field will always be here)</li>
+-- <li><i>user:</i> the IRC username of the user</li>
+-- <li><i>host:</i> the user's hostname</li>
+-- <li><i>realname:</i> the IRC realname of the user</li>
+-- <li><i>server:</i> the IRC server the user is connected to</li>
+-- <li><i>serverinfo:</i> arbitrary information about the above
+-- server</li>
+-- <li><i>awaymsg:</i> set to the user's away message if they are
+-- away</li>
+-- <li><i>is_oper:</i> true if the user is an IRCop</li>
+-- <li><i>idle_time:</i> amount of time the user has been idle</li>
+-- <li><i>channels:</i> array containing the channels the user has
+-- joined</li>
+-- </ul>
+-- @param nick User to request WHOIS information about
function whois(cb, nick)
nick = nick:lower()
requestinfo.whois[nick] = {nick = nick}
-- }}}
-- server_time {{{
+---
+-- Request the current time of the server you are connected to.
+-- @param cb Callback to call when the information is available. The single
+-- table parameter to this callback will contain the fields:
+-- <ul>
+-- <li><i>server:</i> the server which responded to the request</li>
+-- <li><i>time:</i> the time reported by the server</li>
+-- </ul>
function server_time(cb)
-- apparently the optional server parameter isn't supported for servers
-- which you are not directly connected to (freenode specific?)
-- }}}
-- ctcp commands {{{
--- ctcp_ping() - send a CTCP ping request {{{
+-- ctcp_ping {{{
+---
+-- Send a CTCP ping request.
+-- @param cb Callback to call when the information is available. The single
+-- table parameter to this callback will contain the fields:
+-- <ul>
+-- <li><i>nick:</i> the nick which responded to the request</li>
+-- <li><i>time:</i> the roundtrip ping time, in seconds</li>
+-- </ul>
+-- @param nick User to ping
function ctcp_ping(cb, nick)
nick = nick:lower()
if not icallbacks.ctcp_ping[nick] then
end
-- }}}
--- ctcp_time() - send a localtime request {{{
+-- ctcp_time {{{
+---
+-- Send a localtime request.
+-- @param cb Callback to call when the information is available. The single
+-- table parameter to this callback will contain the fields:
+-- <ul>
+-- <li><i>nick:</i> the nick which responded to the request</li>
+-- <li><i>time:</i> the localtime reported by the remote client</li>
+-- </ul>
+-- @param nick User to request the localtime from
function ctcp_time(cb, nick)
nick = nick:lower()
if not icallbacks.ctcp_time[nick] then
end
-- }}}
--- ctcp_version() - send a client version request {{{
+-- ctcp_version {{{
+---
+-- Send a client version request.
+-- @param cb Callback to call when the information is available. The single
+-- table parameter to this callback will contain the fields:
+-- <ul>
+-- <li><i>nick:</i> the nick which responded to the request</li>
+-- <li><i>version:</i> the version reported by the remote client</li>
+-- </ul>
+-- @param nick User to request the client version from
function ctcp_version(cb, nick)
nick = nick:lower()
if not icallbacks.ctcp_version[nick] then
-- }}}
-- 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 :
+-- send {{{
+-- TODO: CTCP quoting should be explicit, this table thing is quite ugly (if
+-- convenient)
+---
+-- Send a raw IRC command.
+-- @param command String containing the raw IRC command
+-- @param ... Arguments to the command. Each argument is either a string or
+-- an array. Strings are sent literally, arrays are CTCP quoted
+-- as a group. The last argument (if it exists) is preceded by
+-- a : (so it may contain spaces).
function send(command, ...)
if not serverinfo.connected and not serverinfo.connecting then return end
local message = command
end
-- }}}
--- get_ip() - get the local ip address for the server connection {{{
+-- get_ip {{{
+---
+-- Get the local IP address for the server connection.
+-- @return A string representation of the local IP address that the IRC server
+-- connection is communicating on
function get_ip()
- return (irc_sock:getsockname())
+ return (ip or irc_sock:getsockname())
end
-- }}}
--- channels() - iterate over currently joined channels {{{
+-- set_ip {{{
+---
+-- Set the local IP manually (to allow for NAT workarounds)
+-- @param new_ip IP address to set
+function set_ip(new_ip)
+ ip = new_ip
+end
+-- }}}
+
+-- channels {{{
+-- TODO: @see doesn't currently work for files/modules
+---
+-- Iterate over currently joined channels.
+-- channels() is an iterator function for use in for loops.
+-- For example, <pre>for chan in irc.channels() do print(chan:name) end</pre>
+-- @see irc.channel
function channels()
return function(state, arg)
return misc.value_iter(state, arg,