]> git.lizzy.rs Git - luairc.git/blobdiff - src/irc.lua
and irc.lua is done
[luairc.git] / src / irc.lua
index 426445833db36b235f857c906885537c6451a09a..12b5408654a3f0d6b253c2a3aee1f07a494b2962 100644 (file)
@@ -44,6 +44,7 @@ local requestinfo = {whois = {}}
 local handlers = {}
 local ctcp_handlers = {}
 local serverinfo = {}
+local ip = nil
 -- }}}
 
 -- defaults {{{
@@ -62,13 +63,13 @@ OUTFILE = nil         -- file to send debug output to - nil is stdout
 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
@@ -77,7 +78,7 @@ local function main_loop_iter()
         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
@@ -95,11 +96,11 @@ 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
 -- }}}
@@ -110,9 +111,9 @@ 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
 -- }}}
 
@@ -121,8 +122,8 @@ 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)
+        serverinfo.channels[chan]:_add_user(from)
+        misc._try_call(on_join, serverinfo.channels[chan], from)
     end
 end
 -- }}}
@@ -133,8 +134,8 @@ function handlers.on_part(from, chan, part_msg)
     -- 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
 -- }}}
@@ -157,15 +158,15 @@ function handlers.on_mode(from, to, mode_string, ...)
             -- 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
@@ -200,14 +201,14 @@ function handlers.on_topic(from, chan, 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])
+        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
 -- }}}
 
@@ -216,29 +217,29 @@ 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)
+        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)
@@ -257,27 +258,27 @@ end
 
 -- 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
@@ -287,9 +288,9 @@ 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
 -- }}}
 
@@ -335,13 +336,13 @@ 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)
+    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
@@ -354,7 +355,7 @@ 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])
+        misc._try_call(on_me_join, serverinfo.channels[chan])
         serverinfo.channels[chan].join_complete = true
     end
 end
@@ -393,7 +394,7 @@ function handlers.on_rpl_endofmotd(from)
     if not serverinfo.connected then
         serverinfo.connected = true
         serverinfo.connecting = false
-        misc.try_call(on_connect)
+        misc._try_call(on_connect)
     end
 end
 -- }}}
@@ -413,7 +414,7 @@ function handlers.on_rpl_whoischannels(from, nick, channel_list)
     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
@@ -488,19 +489,21 @@ 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)
+        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?
@@ -580,7 +583,13 @@ end
 
 -- 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
@@ -596,7 +605,11 @@ function _register_socket(sock, mode, cb)
 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
@@ -617,18 +630,31 @@ 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.
+-- 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
@@ -644,13 +670,15 @@ function connect(args)
     _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)
@@ -658,8 +686,10 @@ function 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)
@@ -667,8 +697,10 @@ function join(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
@@ -676,9 +708,11 @@ function 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
+-- 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 ""
@@ -686,9 +720,11 @@ function say(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
+-- 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 ""
@@ -696,9 +732,11 @@ function notice(name, message)
 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 ""
@@ -709,6 +747,15 @@ end
 
 -- 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?)
@@ -724,6 +771,28 @@ end
 
 -- 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}
@@ -737,6 +806,14 @@ end
 -- }}}
 
 -- 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?)
@@ -752,7 +829,16 @@ end
 -- }}}
 
 -- 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
@@ -764,7 +850,16 @@ function ctcp_ping(cb, nick)
 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
@@ -776,7 +871,16 @@ function ctcp_time(cb, nick)
 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
@@ -790,11 +894,16 @@ 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 :
+-- 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
@@ -805,34 +914,53 @@ function send(command, ...)
         if base.type(v) == "string" then
             arg = v
         elseif base.type(v) == "table" then
-            arg = ctcp.ctcp_quote(table.concat(v, " "))
+            arg = ctcp._ctcp_quote(table.concat(v, " "))
         end
         if i == #{...} then
             arg = ":" .. arg
         end
         message = message .. " " .. arg
     end
-    message = ctcp.low_quote(message)
+    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_debug._message("SEND", message)
     irc_sock:send(message .. "\r\n")
 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,
-                                      function(v)
-                                          return v.join_complete
-                                      end)
+               return misc._value_iter(state, arg,
+                                       function(v)
+                                           return v.join_complete
+                                       end)
            end,
            serverinfo.channels,
            nil