2 -- Implementation of the main LuaIRC module
5 -- LuaIRC - IRC framework written in Lua
10 irc._VERSION = 'LuaIRC 0.3 (Lua 5.3 Port)'
17 libs.socket = require 'socket'
19 local old_libs = _G.libs
22 libs.constants = require 'irc.constants'
23 libs.ctcp = require 'irc.ctcp'
24 libs.debug = require 'irc.debug'
25 libs.misc = require 'irc.misc'
26 libs.channel = require 'irc.channel'
27 libs.dcc = require 'irc.dcc'
28 libs.message = require 'irc.message'
32 -- localize modules {{{
33 local constants = libs.constants
34 local ctcp = libs.ctcp
35 local c = ctcp._ctcp_quote
36 local irc_debug = libs.debug
37 local message = libs.message
38 local misc = libs.misc
39 local socket = libs.socket
43 local Channel = libs.channel
46 -- local variables {{{
60 local requestinfo = {whois = {}}
62 local ctcp_handlers = {}
63 local user_handlers = {}
69 irc.TIMEOUT = 60 -- connection timeout
70 irc.NETWORK = "localhost" -- default network
71 irc.PORT = 6667 -- default port
72 irc.NICK = "luabot" -- default nick
73 irc.USERNAME = "LuaIRC" -- default username
74 irc.REALNAME = "LuaIRC" -- default realname
75 irc.DEBUG = false -- whether we want extra debug information
76 irc.OUTFILE = nil -- file to send debug output to - nil is stdout
79 -- private functions {{{
81 local function main_loop_iter()
82 if #rsockets == 0 and #wsockets == 0 then return false end
83 local rready, wready, err = socket.select(rsockets, wsockets)
84 if err then irc_debug._err(err); return false; end
86 for _, sock in ipairs(rready) do
87 local cb = socket.protect(rcallbacks[sock])
88 local ret, err = cb(sock)
90 irc_debug._warn("socket error: " .. err)
91 irc._unregister_socket(sock, 'r')
95 for _, sock in ipairs(wready) do
96 local cb = socket.protect(wcallbacks[sock])
97 local ret, err = cb(sock)
99 irc_debug._warn("socket error: " .. err)
100 irc._unregister_socket(sock, 'w')
108 -- begin_main_loop {{{
109 local function begin_main_loop()
110 while main_loop_iter() do end
114 -- incoming_message {{{
115 local function incoming_message(sock)
116 local raw_msg = socket.try(sock:receive())
117 irc_debug._message("RECV", raw_msg)
118 local msg = message._parse(raw_msg)
119 misc._try_call_warn("Unhandled server message: " .. msg.command,
120 handlers["on_" .. msg.command:lower()],
121 (misc._parse_user(msg.from)), table.unpack(msg.args))
127 local function callback(name, ...)
128 return misc._try_call(user_handlers[name], ...)
133 -- internal message handlers {{{
134 -- command handlers {{{
136 function handlers.on_nick(from, new_nick)
137 for chan in irc.channels() do
138 chan:_change_nick(from, new_nick)
140 callback("nick_change", new_nick, from)
145 function handlers.on_join(from, chan)
146 assert(serverinfo.channels[chan],
147 "Received join message for unknown channel: " .. chan)
148 if serverinfo.channels[chan].join_complete then
149 serverinfo.channels[chan]:_add_user(from)
150 callback("join", serverinfo.channels[chan], from)
156 function handlers.on_part(from, chan, part_msg)
157 -- don't assert on chan here, since we get part messages for ourselves
158 -- after we remove the channel from the channel list
159 if not serverinfo.channels[chan] then return end
160 if serverinfo.channels[chan].join_complete then
161 serverinfo.channels[chan]:_remove_user(from)
162 callback("part", serverinfo.channels[chan], from, part_msg)
168 function handlers.on_mode(from, to, mode_string, ...)
169 local dir = mode_string:sub(1, 1)
170 mode_string = mode_string:sub(2)
173 if to:sub(1, 1) == "#" then
174 -- handle channel mode requests {{{
175 assert(serverinfo.channels[to],
176 "Received mode change for unknown channel: " .. to)
177 local chan = serverinfo.channels[to]
179 for i = 1, mode_string:len() do
180 local mode = mode_string:sub(i, i)
181 local target = args[ind]
182 -- channel modes other than op/voice will be implemented as
183 -- information request commands
184 if mode == "o" then -- channel op {{{
185 chan:_change_status(target, dir == "+", "o")
186 callback(({["+"] = "op", ["-"] = "deop"})[dir],
190 elseif mode == "v" then -- voice {{{
191 chan:_change_status(target, dir == "+", "v")
192 callback(({["+"] = "voice", ["-"] = "devoice"})[dir],
199 elseif from == to then
200 -- handle user mode requests {{{
201 -- TODO: make users more easily accessible so this is actually
202 -- reasonably possible
203 for i = 1, mode_string:len() do
204 local mode = mode_string:sub(i, i)
205 if mode == "i" then -- invisible {{{
207 elseif mode == "s" then -- server messages {{{
209 elseif mode == "w" then -- wallops messages {{{
211 elseif mode == "o" then -- ircop {{{
221 function handlers.on_topic(from, chan, new_topic)
222 assert(serverinfo.channels[chan],
223 "Received topic message for unknown channel: " .. chan)
224 serverinfo.channels[chan]._topic.text = new_topic
225 serverinfo.channels[chan]._topic.user = from
226 serverinfo.channels[chan]._topic.time = os.time()
227 if serverinfo.channels[chan].join_complete then
228 callback("topic_change", serverinfo.channels[chan])
234 function handlers.on_invite(from, to, chan)
235 callback("invite", from, chan)
240 function handlers.on_kick(from, chan, to)
241 assert(serverinfo.channels[chan],
242 "Received kick message for unknown channel: " .. chan)
243 if serverinfo.channels[chan].join_complete then
244 serverinfo.channels[chan]:_remove_user(to)
245 callback("kick", serverinfo.channels[chan], to, from)
251 function handlers.on_privmsg(from, to, msg)
252 local msgs = ctcp._ctcp_split(msg)
253 for _, v in ipairs(msgs) do
257 local words = misc._split(msg)
258 local received_command = words[1]
259 local cb = "on_" .. received_command:lower()
260 table.remove(words, 1)
261 -- not using try_call here because the ctcp specification requires
262 -- an error response to nonexistant commands
263 if type(ctcp_handlers[cb]) == "function" then
264 ctcp_handlers[cb](from, to, table.concat(words, " "))
266 irc.notice(from, c("ERRMSG", received_command, ":Unknown query"))
270 -- normal message {{{
271 if to:sub(1, 1) == "#" then
272 assert(serverinfo.channels[to],
273 "Received channel msg from unknown channel: " .. to)
274 callback("channel_msg", serverinfo.channels[to], from, msg)
276 callback("private_msg", from, msg)
285 function handlers.on_notice(from, to, msg)
286 local msgs = ctcp._ctcp_split(msg)
287 for _, v in ipairs(msgs) do
291 local words = misc._split(msg)
292 local command = words[1]:lower()
293 table.remove(words, 1)
294 misc._try_call_warn("Unknown CTCP message: " .. command,
295 ctcp_handlers["on_rpl_"..command], from, to,
296 table.concat(words, ' '))
299 -- normal message {{{
300 if to:sub(1, 1) == "#" then
301 assert(serverinfo.channels[to],
302 "Received channel msg from unknown channel: " .. to)
303 callback("channel_notice", serverinfo.channels[to], from, msg)
305 callback("private_notice", from, msg)
314 function handlers.on_quit(from, quit_msg)
315 for name, chan in pairs(serverinfo.channels) do
316 chan:_remove_user(from)
318 callback("quit", from, quit_msg)
323 -- respond to server pings to make sure it knows we are alive
324 function handlers.on_ping(from, respond_to)
325 irc.send("PONG", respond_to)
330 -- server replies {{{
332 -- catch topic changes
333 function handlers.on_rpl_topic(from, chan, topic)
334 assert(serverinfo.channels[chan],
335 "Received topic information about unknown channel: " .. chan)
336 serverinfo.channels[chan]._topic.text = topic
340 -- on_rpl_notopic {{{
341 function handlers.on_rpl_notopic(from, chan)
342 assert(serverinfo.channels[chan],
343 "Received topic information about unknown channel: " .. chan)
344 serverinfo.channels[chan]._topic.text = ""
348 -- on_rpl_topicdate {{{
349 -- "topic was set by <user> at <time>"
350 function handlers.on_rpl_topicdate(from, chan, user, time)
351 assert(serverinfo.channels[chan],
352 "Received topic information about unknown channel: " .. chan)
353 serverinfo.channels[chan]._topic.user = user
354 serverinfo.channels[chan]._topic.time = tonumber(time)
358 -- on_rpl_namreply {{{
359 -- handles a NAMES reply
360 function handlers.on_rpl_namreply(from, chanmode, chan, userlist)
361 assert(serverinfo.channels[chan],
362 "Received user information about unknown channel: " .. chan)
363 serverinfo.channels[chan]._chanmode = constants.chanmodes[chanmode]
364 local users = misc._split(userlist)
365 for k,v in ipairs(users) do
366 if v:sub(1, 1) == "@" or v:sub(1, 1) == "+" then
367 local nick = v:sub(2)
368 serverinfo.channels[chan]:_add_user(nick, v:sub(1, 1))
370 serverinfo.channels[chan]:_add_user(v)
376 -- on_rpl_endofnames {{{
377 -- when we get this message, the channel join has completed, so call the
379 function handlers.on_rpl_endofnames(from, chan)
380 assert(serverinfo.channels[chan],
381 "Received user information about unknown channel: " .. chan)
382 if not serverinfo.channels[chan].join_complete then
383 callback("me_join", serverinfo.channels[chan])
384 serverinfo.channels[chan].join_complete = true
389 -- on_rpl_welcome {{{
390 function handlers.on_rpl_welcome(from)
399 -- on_rpl_yourhost {{{
400 function handlers.on_rpl_yourhost(from, msg)
401 serverinfo.host = from
405 -- on_rpl_motdstart {{{
406 function handlers.on_rpl_motdstart(from)
412 function handlers.on_rpl_motd(from, motd)
413 serverinfo.motd = (serverinfo.motd or "") .. motd .. "\n"
417 -- on_rpl_endofmotd {{{
418 function handlers.on_rpl_endofmotd(from)
419 if not serverinfo.connected then
420 serverinfo.connected = true
421 serverinfo.connecting = false
427 -- on_rpl_whoisuser {{{
428 function handlers.on_rpl_whoisuser(from, nick, user, host, star, realname)
429 local lnick = nick:lower()
430 requestinfo.whois[lnick].nick = nick
431 requestinfo.whois[lnick].user = user
432 requestinfo.whois[lnick].host = host
433 requestinfo.whois[lnick].realname = realname
437 -- on_rpl_whoischannels {{{
438 function handlers.on_rpl_whoischannels(from, nick, channel_list)
440 if not requestinfo.whois[nick].channels then
441 requestinfo.whois[nick].channels = {}
443 for _, channel in ipairs(misc._split(channel_list)) do
444 table.insert(requestinfo.whois[nick].channels, channel)
449 -- on_rpl_whoisserver {{{
450 function handlers.on_rpl_whoisserver(from, nick, server, serverinfo)
452 requestinfo.whois[nick].server = server
453 requestinfo.whois[nick].serverinfo = serverinfo
458 function handlers.on_rpl_away(from, nick, away_msg)
460 if requestinfo.whois[nick] then
461 requestinfo.whois[nick].away_msg = away_msg
466 -- on_rpl_whoisoperator {{{
467 function handlers.on_rpl_whoisoperator(from, nick)
468 requestinfo.whois[nick:lower()].is_oper = true
472 -- on_rpl_whoisidle {{{
473 function handlers.on_rpl_whoisidle(from, nick, idle_seconds)
474 requestinfo.whois[nick:lower()].idle_time = idle_seconds
478 -- on_rpl_endofwhois {{{
479 function handlers.on_rpl_endofwhois(from, nick)
481 local cb = table.remove(icallbacks.whois[nick], 1)
482 cb(requestinfo.whois[nick])
483 requestinfo.whois[nick] = nil
484 if #icallbacks.whois[nick] > 0 then irc.send("WHOIS", nick)
485 else icallbacks.whois[nick] = nil
490 -- on_rpl_version {{{
491 function handlers.on_rpl_version(from, version, server, comments)
492 local cb = table.remove(icallbacks.serverversion[server], 1)
493 cb({version = version, server = server, comments = comments})
494 if #icallbacks.serverversion[server] > 0 then irc.send("VERSION", server)
495 else icallbacks.serverversion[server] = nil
501 function on_rpl_time(from, server, time)
502 local cb = table.remove(icallbacks.servertime[server], 1)
503 cb({time = time, server = server})
504 if #icallbacks.servertime[server] > 0 then irc.send("TIME", server)
505 else icallbacks.servertime[server] = nil
514 function ctcp_handlers.on_action(from, to, message)
515 if to:sub(1, 1) == "#" then
516 assert(serverinfo.channels[to],
517 "Received channel msg from unknown channel: " .. to)
518 callback("channel_act", serverinfo.channels[to], from, message)
520 callback("private_act", from, message)
526 -- TODO: can we not have this handler be registered unless the dcc module is
528 function ctcp_handlers.on_dcc(from, to, message)
529 local type, argument, address, port, size = table.unpack(misc._split(message, " ", nil, '"', '"'))
530 address = misc._ip_int_to_str(address)
531 if type == "SEND" then
532 if callback("dcc_send", from, to, argument, address, port, size) then
533 dcc._accept(argument, address, port)
535 elseif type == "CHAT" then
536 -- TODO: implement this? do people ever use this?
542 function ctcp_handlers.on_version(from, to)
543 irc.notice(from, c("VERSION", irc._VERSION .. " running under " .. _VERSION .. " with " .. socket._VERSION))
548 function ctcp_handlers.on_errmsg(from, to, message)
549 irc.notice(from, c("ERRMSG", message, ":No error has occurred"))
554 function ctcp_handlers.on_ping(from, to, timestamp)
555 irc.notice(from, c("PING", timestamp))
560 function ctcp_handlers.on_time(from, to)
561 irc.notice(from, c("TIME", os.date()))
568 -- actions are handled the same, notice or not
569 ctcp_handlers.on_rpl_action = ctcp_handlers.on_action
572 -- on_rpl_version {{{
573 function ctcp_handlers.on_rpl_version(from, to, version)
574 local lfrom = from:lower()
575 local cb = table.remove(icallbacks.ctcp_version[lfrom], 1)
576 cb({version = version, nick = from})
577 if #icallbacks.ctcp_version[lfrom] > 0 then irc.say(from, c("VERSION"))
578 else icallbacks.ctcp_version[lfrom] = nil
584 function ctcp_handlers.on_rpl_errmsg(from, to, message)
585 callback("ctcp_error", from, to, message)
590 function ctcp_handlers.on_rpl_ping(from, to, timestamp)
591 local lfrom = from:lower()
592 local cb = table.remove(icallbacks.ctcp_ping[lfrom], 1)
593 cb({time = os.time() - timestamp, nick = from})
594 if #icallbacks.ctcp_ping[lfrom] > 0 then irc.say(from, c("PING", os.time()))
595 else icallbacks.ctcp_ping[lfrom] = nil
601 function ctcp_handlers.on_rpl_time(from, to, time)
602 local lfrom = from:lower()
603 local cb = table.remove(icallbacks.ctcp_time[lfrom], 1)
604 cb({time = time, nick = from})
605 if #icallbacks.ctcp_time[lfrom] > 0 then irc.say(from, c("TIME"))
606 else icallbacks.ctcp_time[lfrom] = nil
614 -- module functions {{{
615 -- socket handling functions {{{
616 -- _register_socket {{{
618 -- Register a socket to listen on.
619 -- @param sock LuaSocket socket object
620 -- @param mode 'r' if the socket is for reading, 'w' if for writing
621 -- @param cb Callback to call when the socket is ready for reading/writing.
622 -- It will be called with the socket as the single argument.
623 function irc._register_socket(sock, mode, cb)
632 assert(not cbs[sock], "socket already registered")
633 table.insert(socks, sock)
638 -- _unregister_socket {{{
640 -- Remove a previously registered socket.
641 -- @param sock Socket to unregister
642 -- @param mode 'r' to unregister it for reading, 'w' for writing
643 function irc._unregister_socket(sock, mode)
652 for i, v in ipairs(socks) do
653 if v == sock then table.remove(socks, i); break; end
661 -- public functions {{{
662 -- server commands {{{
665 -- Start a connection to the irc server.
666 -- @param args Table of named arguments containing connection parameters.
667 -- Defaults are the all-caps versions of these parameters given
668 -- at the top of the file, and are overridable by setting them
669 -- as well, i.e. <pre>irc.NETWORK = irc.freenode.net</pre>
670 -- Possible options are:
672 -- <li><i>network:</i> address of the irc network to connect to
673 -- (default: 'localhost')</li>
674 -- <li><i>port:</i> port to connect to
675 -- (default: '6667')</li>
676 -- <li><i>pass:</i> irc server password
677 -- (default: don't send)</li>
678 -- <li><i>nick:</i> nickname to connect as
679 -- (default: 'luabot')</li>
680 -- <li><i>username:</i> username to connect with
681 -- (default: 'LuaIRC')</li>
682 -- <li><i>realname:</i> realname to connect with
683 -- (default: 'LuaIRC')</li>
684 -- <li><i>timeout:</i> amount of time in seconds to wait before
685 -- dropping an idle connection
686 -- (default: '60')</li>
688 function irc.connect(args)
689 local network = args.network or irc.NETWORK
690 local port = args.port or irc.PORT
691 local nick = args.nick or irc.NICK
692 local username = args.username or irc.USERNAME
693 local realname = args.realname or irc.REALNAME
694 local timeout = args.timeout or irc.TIMEOUT
695 serverinfo.connecting = true
696 if irc.OUTFILE then irc_debug.set_output(irc.OUTFILE) end
697 if irc.DEBUG then irc_debug.enable() end
698 irc_sock = assert(socket.connect(network, port))
699 irc_sock:settimeout(timeout)
700 irc._register_socket(irc_sock, 'r', incoming_message)
701 if args.pass then irc.send("PASS", args.pass) end
702 irc.send("NICK", nick)
703 irc.send("USER", username, irc.get_ip(), network, realname)
710 -- Close the connection to the irc server.
711 -- @param message Quit message (optional, defaults to 'Leaving')
712 function irc.quit(message)
713 message = message or "Leaving"
714 irc.send("QUIT", message)
715 serverinfo.connected = false
722 -- @param channel Channel to join
723 function irc.join(channel)
724 if not channel then return end
725 serverinfo.channels[channel] = Channel.new(channel)
726 irc.send("JOIN", channel)
733 -- @param channel Channel to leave
734 function irc.part(channel)
735 if not channel then return end
736 serverinfo.channels[channel] = nil
737 irc.send("PART", channel)
743 -- Send a message to a user or channel.
744 -- @param name User or channel to send the message to
745 -- @param message Message to send
746 function irc.say(name, message)
747 if not name then return end
748 message = message or ""
749 irc.send("PRIVMSG", name, message)
755 -- Send a notice to a user or channel.
756 -- @param name User or channel to send the notice to
757 -- @param message Message to send
758 function irc.notice(name, message)
759 if not name then return end
760 message = message or ""
761 irc.send("NOTICE", name, message)
767 -- Perform a /me action.
768 -- @param name User or channel to send the action to
769 -- @param action Action to send
770 function irc.act(name, action)
771 if not name then return end
772 action = action or ""
773 irc.send("PRIVMSG", name, c("ACTION", action))
778 -- information requests {{{
779 -- server_version {{{
781 -- Request the version of the IRC server you are currently connected to.
782 -- @param cb Callback to call when the information is available. The single
783 -- table parameter to this callback will contain the fields:
785 -- <li><i>server:</i> the server which responded to the request</li>
786 -- <li><i>version:</i> the server version</li>
787 -- <li><i>comments:</i> other data provided by the server</li>
789 function irc.server_version(cb)
790 -- apparently the optional server parameter isn't supported for servers
791 -- which you are not directly connected to (freenode specific?)
792 local server = serverinfo.host
793 if not icallbacks.serverversion[server] then
794 icallbacks.serverversion[server] = {cb}
795 irc.send("VERSION", server)
797 table.insert(icallbacks.serverversion[server], cb)
803 -- TODO: allow server parameter (to get user idle time)
805 -- Request WHOIS information about a given user.
806 -- @param cb Callback to call when the information is available. The single
807 -- table parameter to this callback may contain any or all of the
810 -- <li><i>nick:</i> the nick that was passed to this function
811 -- (this field will always be here)</li>
812 -- <li><i>user:</i> the IRC username of the user</li>
813 -- <li><i>host:</i> the user's hostname</li>
814 -- <li><i>realname:</i> the IRC realname of the user</li>
815 -- <li><i>server:</i> the IRC server the user is connected to</li>
816 -- <li><i>serverinfo:</i> arbitrary information about the above
818 -- <li><i>awaymsg:</i> set to the user's away message if they are
820 -- <li><i>is_oper:</i> true if the user is an IRCop</li>
821 -- <li><i>idle_time:</i> amount of time the user has been idle</li>
822 -- <li><i>channels:</i> array containing the channels the user has
825 -- @param nick User to request WHOIS information about
826 function irc.whois(cb, nick)
828 requestinfo.whois[nick] = {}
829 if not icallbacks.whois[nick] then
830 icallbacks.whois[nick] = {cb}
831 irc.send("WHOIS", nick)
833 table.insert(icallbacks.whois[nick], cb)
840 -- Request the current time of the server you are connected to.
841 -- @param cb Callback to call when the information is available. The single
842 -- table parameter to this callback will contain the fields:
844 -- <li><i>server:</i> the server which responded to the request</li>
845 -- <li><i>time:</i> the time reported by the server</li>
847 function irc.server_time(cb)
848 -- apparently the optional server parameter isn't supported for servers
849 -- which you are not directly connected to (freenode specific?)
850 local server = serverinfo.host
851 if not icallbacks.servertime[server] then
852 icallbacks.servertime[server] = {cb}
853 irc.send("TIME", server)
855 table.insert(icallbacks.servertime[server], cb)
864 -- Send a CTCP ping request.
865 -- @param cb Callback to call when the information is available. The single
866 -- table parameter to this callback will contain the fields:
868 -- <li><i>nick:</i> the nick which responded to the request</li>
869 -- <li><i>time:</i> the roundtrip ping time, in seconds</li>
871 -- @param nick User to ping
872 function irc.ctcp_ping(cb, nick)
874 if not icallbacks.ctcp_ping[nick] then
875 icallbacks.ctcp_ping[nick] = {cb}
876 irc.say(nick, c("PING", os.time()))
878 table.insert(icallbacks.ctcp_ping[nick], cb)
885 -- Send a localtime request.
886 -- @param cb Callback to call when the information is available. The single
887 -- table parameter to this callback will contain the fields:
889 -- <li><i>nick:</i> the nick which responded to the request</li>
890 -- <li><i>time:</i> the localtime reported by the remote client</li>
892 -- @param nick User to request the localtime from
893 function irc.ctcp_time(cb, nick)
895 if not icallbacks.ctcp_time[nick] then
896 icallbacks.ctcp_time[nick] = {cb}
897 irc.say(nick, c("TIME"))
899 table.insert(icallbacks.ctcp_time[nick], cb)
906 -- Send a client version request.
907 -- @param cb Callback to call when the information is available. The single
908 -- table parameter to this callback will contain the fields:
910 -- <li><i>nick:</i> the nick which responded to the request</li>
911 -- <li><i>version:</i> the version reported by the remote client</li>
913 -- @param nick User to request the client version from
914 function irc.ctcp_version(cb, nick)
916 if not icallbacks.ctcp_version[nick] then
917 icallbacks.ctcp_version[nick] = {cb}
918 irc.say(nick, c("VERSION"))
920 table.insert(icallbacks.ctcp_version[nick], cb)
926 -- callback functions {{{
927 -- register_callback {{{
929 -- Register a user function to be called when a specific event occurs.
930 -- @param name Name of the event
931 -- @param fn Function to call when the event occurs, or nil to clear the
932 -- callback for this event
933 -- @return Value of the original callback for this event (or nil if no previous
934 -- callback had been set)
935 function irc.register_callback(name, fn)
936 local old_handler = user_handlers[name]
937 user_handlers[name] = fn
943 -- misc functions {{{
945 -- TODO: CTCP quoting should be explicit, this table thing is quite ugly (if
948 -- Send a raw IRC command.
949 -- @param command String containing the raw IRC command
950 -- @param ... Arguments to the command. Each argument is either a string or
951 -- an array. Strings are sent literally, arrays are CTCP quoted
952 -- as a group. The last argument (if it exists) is preceded by
953 -- a : (so it may contain spaces).
954 function irc.send(command, ...)
955 if not serverinfo.connected and not serverinfo.connecting then return end
956 local message = command
957 for i, v in ipairs({...}) do
961 message = message .. " " .. v
963 message = ctcp._low_quote(message)
964 -- we just truncate for now. -2 to account for the \r\n
965 message = message:sub(1, constants.IRC_MAX_MSG - 2)
966 irc_debug._message("SEND", message)
967 irc_sock:send(message .. "\r\n")
973 -- Get the local IP address for the server connection.
974 -- @return A string representation of the local IP address that the IRC server
975 -- connection is communicating on
976 function irc.get_ip()
977 return (ip or irc_sock:getsockname())
983 -- Set the local IP manually (to allow for NAT workarounds)
984 -- @param new_ip IP address to set
985 function irc.set_ip(new_ip)
991 -- TODO: @see doesn't currently work for files/modules
993 -- Iterate over currently joined channels.
994 -- channels() is an iterator function for use in for loops.
995 -- For example, <pre>for chan in irc.channels() do print(chan:name) end</pre>
997 function irc.channels()
998 return function(state, arg)
999 return misc._value_iter(state, arg,
1001 return v.join_complete
1004 serverinfo.channels,