Port to Lua 5.3

master
Elias Fleckenstein 2021-11-21 19:16:03 +01:00
parent 09628cded4
commit 783fefdcb3
No known key found for this signature in database
GPG Key ID: 06927A5199D6C9B2
11 changed files with 270 additions and 258 deletions

View File

@ -1,5 +1,5 @@
# Path to your Lua library directory (LUA_PATH) # Path to your Lua library directory (LUA_PATH)
LUA_DIR = /usr/local/share/lua/5.1 LUA_DIR = /usr/local/share/lua/5.3
# Set this to the path of your luadoc executable if you want to regenerate the # Set this to the path of your luadoc executable if you want to regenerate the
# documentation and the luadoc script isn't in your PATH # documentation and the luadoc script isn't in your PATH

9
README
View File

@ -1,5 +1,6 @@
LuaIRC v0.3 LuaIRC v0.3 (Lua 5.3 Port)
Jesse Luehrs (jluehrs2@uiuc.edu) Jesse Luehrs (jluehrs2@uiuc.edu)
Elias Fleckenstein (fleckenstein@elidragon.com)
OVERVIEW OVERVIEW
======== ========
@ -7,16 +8,18 @@ LuaIRC is a fully featured IRC framework written entirely in Lua. It provides an
INSTALL INSTALL
======= =======
This module requires LuaSocket (http://www.cs.princeton.edu/~diego/professional/luasocket/) and Lua 5.1. To install, modify the Make.config file with paths appropriate to your system and run 'make install'. This module requires LuaSocket (http://www.cs.princeton.edu/~diego/professional/luasocket/) and Lua 5.3. To install, modify the Make.config file with paths appropriate to your system and run 'make install'.
DOCUMENTATION DOCUMENTATION
============= =============
Documentation of the API can be found in the doc/ directory. It was autogenerated from the source files by LuaDoc (http://luadoc.luaforge.net/). Documentation of the API can be found in the doc/ directory. It was autogenerated from the source files by LuaDoc (http://luadoc.luaforge.net/).
LuaIRC has only been tested on Freenode so far, but I plan to expand this to other servers in the future. It's quite possible that it works on other servers anyway, however, so feel free to try it out, and send in bug reports for things that break. LuaIRC has only been tested on Freenode and Libera.chat so far, but I plan to expand this to other servers in the future. It's quite possible that it works on other servers anyway, however, so feel free to try it out, and send in bug reports for things that break.
CHANGES CHANGES
======= =======
0.3 (Lua 5.3 Port)
- Unofficial port to work with Lua 5.3
0.3 0.3
- Major cleanup and restructuring again, documentation added, first public release - Major cleanup and restructuring again, documentation added, first public release
0.2 0.2

View File

@ -1,31 +1,46 @@
--- ---
-- Implementation of the main LuaIRC module -- Implementation of the main LuaIRC module
-- initialization {{{
local base = _G
local constants = require 'irc.constants'
local ctcp = require 'irc.ctcp'
local c = ctcp._ctcp_quote
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'
-- }}}
--- ---
-- LuaIRC - IRC framework written in Lua -- LuaIRC - IRC framework written in Lua
-- @release 0.3 -- @release 0.3
module 'irc' local irc = {}
-- constants {{{ -- constants {{{
_VERSION = 'LuaIRC 0.3' irc._VERSION = 'LuaIRC 0.3 (Lua 5.3 Port)'
-- }}}
-- libraries {{{
local libs = {}
libs.irc = irc
libs.socket = require 'socket'
local old_libs = _G.libs
_G.libs = libs
libs.constants = require 'irc.constants'
libs.ctcp = require 'irc.ctcp'
libs.debug = require 'irc.debug'
libs.misc = require 'irc.misc'
libs.channel = require 'irc.channel'
libs.dcc = require 'irc.dcc'
libs.message = require 'irc.message'
_G.libs = old_libs
-- localize modules {{{
local constants = libs.constants
local ctcp = libs.ctcp
local c = ctcp._ctcp_quote
local irc_debug = libs.debug
local message = libs.message
local misc = libs.misc
local socket = libs.socket
-- }}} -- }}}
-- classes {{{ -- classes {{{
local Channel = base.require 'irc.channel' local Channel = libs.channel
-- }}} -- }}}
-- local variables {{{ -- local variables {{{
@ -51,14 +66,14 @@ local ip = nil
-- }}} -- }}}
-- defaults {{{ -- defaults {{{
TIMEOUT = 60 -- connection timeout irc.TIMEOUT = 60 -- connection timeout
NETWORK = "localhost" -- default network irc.NETWORK = "localhost" -- default network
PORT = 6667 -- default port irc.PORT = 6667 -- default port
NICK = "luabot" -- default nick irc.NICK = "luabot" -- default nick
USERNAME = "LuaIRC" -- default username irc.USERNAME = "LuaIRC" -- default username
REALNAME = "LuaIRC" -- default realname irc.REALNAME = "LuaIRC" -- default realname
DEBUG = false -- whether we want extra debug information irc.DEBUG = false -- whether we want extra debug information
OUTFILE = nil -- file to send debug output to - nil is stdout irc.OUTFILE = nil -- file to send debug output to - nil is stdout
-- }}} -- }}}
-- private functions {{{ -- private functions {{{
@ -68,21 +83,21 @@ local function main_loop_iter()
local rready, wready, err = socket.select(rsockets, wsockets) 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 for _, sock in ipairs(rready) do
local cb = socket.protect(rcallbacks[sock]) local cb = socket.protect(rcallbacks[sock])
local ret, err = cb(sock) local ret, err = cb(sock)
if not ret then if not ret then
irc_debug._warn("socket error: " .. err) irc_debug._warn("socket error: " .. err)
_unregister_socket(sock, 'r') irc._unregister_socket(sock, 'r')
end end
end end
for _, sock in base.ipairs(wready) do for _, sock in ipairs(wready) do
local cb = socket.protect(wcallbacks[sock]) local cb = socket.protect(wcallbacks[sock])
local ret, err = cb(sock) local ret, err = cb(sock)
if not ret then if not ret then
irc_debug._warn("socket error: " .. err) irc_debug._warn("socket error: " .. err)
_unregister_socket(sock, 'w') irc._unregister_socket(sock, 'w')
end end
end end
@ -103,7 +118,7 @@ local function incoming_message(sock)
local msg = message._parse(raw_msg) local msg = message._parse(raw_msg)
misc._try_call_warn("Unhandled server message: " .. msg.command, misc._try_call_warn("Unhandled server message: " .. msg.command,
handlers["on_" .. msg.command:lower()], handlers["on_" .. msg.command:lower()],
(misc._parse_user(msg.from)), base.unpack(msg.args)) (misc._parse_user(msg.from)), table.unpack(msg.args))
return true return true
end end
-- }}} -- }}}
@ -119,7 +134,7 @@ end
-- command handlers {{{ -- command handlers {{{
-- on_nick {{{ -- on_nick {{{
function handlers.on_nick(from, new_nick) function handlers.on_nick(from, new_nick)
for chan in channels() do for chan in irc.channels() do
chan:_change_nick(from, new_nick) chan:_change_nick(from, new_nick)
end end
callback("nick_change", new_nick, from) callback("nick_change", new_nick, from)
@ -128,7 +143,7 @@ end
-- on_join {{{ -- on_join {{{
function handlers.on_join(from, chan) function handlers.on_join(from, chan)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received join message for unknown channel: " .. chan) "Received join message for unknown channel: " .. chan)
if serverinfo.channels[chan].join_complete then if serverinfo.channels[chan].join_complete then
serverinfo.channels[chan]:_add_user(from) serverinfo.channels[chan]:_add_user(from)
@ -157,7 +172,7 @@ function handlers.on_mode(from, to, mode_string, ...)
if to:sub(1, 1) == "#" then if to:sub(1, 1) == "#" then
-- handle channel mode requests {{{ -- handle channel mode requests {{{
base.assert(serverinfo.channels[to], assert(serverinfo.channels[to],
"Received mode change for unknown channel: " .. to) "Received mode change for unknown channel: " .. to)
local chan = serverinfo.channels[to] local chan = serverinfo.channels[to]
local ind = 1 local ind = 1
@ -204,7 +219,7 @@ end
-- on_topic {{{ -- on_topic {{{
function handlers.on_topic(from, chan, new_topic) function handlers.on_topic(from, chan, new_topic)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received topic message for unknown channel: " .. chan) "Received topic message for unknown channel: " .. chan)
serverinfo.channels[chan]._topic.text = new_topic serverinfo.channels[chan]._topic.text = new_topic
serverinfo.channels[chan]._topic.user = from serverinfo.channels[chan]._topic.user = from
@ -223,7 +238,7 @@ end
-- on_kick {{{ -- on_kick {{{
function handlers.on_kick(from, chan, to) function handlers.on_kick(from, chan, to)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received kick message for unknown channel: " .. chan) "Received kick message for unknown channel: " .. chan)
if serverinfo.channels[chan].join_complete then if serverinfo.channels[chan].join_complete then
serverinfo.channels[chan]:_remove_user(to) serverinfo.channels[chan]:_remove_user(to)
@ -235,7 +250,7 @@ end
-- on_privmsg {{{ -- on_privmsg {{{
function handlers.on_privmsg(from, to, msg) function handlers.on_privmsg(from, to, msg)
local msgs = ctcp._ctcp_split(msg) local msgs = ctcp._ctcp_split(msg)
for _, v in base.ipairs(msgs) do for _, v in ipairs(msgs) do
local msg = v.str local msg = v.str
if v.ctcp then if v.ctcp then
-- ctcp message {{{ -- ctcp message {{{
@ -245,16 +260,16 @@ function handlers.on_privmsg(from, to, msg)
table.remove(words, 1) table.remove(words, 1)
-- not using try_call here because the ctcp specification requires -- not using try_call here because the ctcp specification requires
-- an error response to nonexistant commands -- an error response to nonexistant commands
if base.type(ctcp_handlers[cb]) == "function" then if type(ctcp_handlers[cb]) == "function" then
ctcp_handlers[cb](from, to, table.concat(words, " ")) ctcp_handlers[cb](from, to, table.concat(words, " "))
else else
notice(from, c("ERRMSG", received_command, ":Unknown query")) irc.notice(from, c("ERRMSG", received_command, ":Unknown query"))
end end
-- }}} -- }}}
else else
-- normal message {{{ -- normal message {{{
if to:sub(1, 1) == "#" then if to:sub(1, 1) == "#" then
base.assert(serverinfo.channels[to], assert(serverinfo.channels[to],
"Received channel msg from unknown channel: " .. to) "Received channel msg from unknown channel: " .. to)
callback("channel_msg", serverinfo.channels[to], from, msg) callback("channel_msg", serverinfo.channels[to], from, msg)
else else
@ -269,7 +284,7 @@ end
-- on_notice {{{ -- on_notice {{{
function handlers.on_notice(from, to, msg) function handlers.on_notice(from, to, msg)
local msgs = ctcp._ctcp_split(msg) local msgs = ctcp._ctcp_split(msg)
for _, v in base.ipairs(msgs) do for _, v in ipairs(msgs) do
local msg = v.str local msg = v.str
if v.ctcp then if v.ctcp then
-- ctcp message {{{ -- ctcp message {{{
@ -283,7 +298,7 @@ function handlers.on_notice(from, to, msg)
else else
-- normal message {{{ -- normal message {{{
if to:sub(1, 1) == "#" then if to:sub(1, 1) == "#" then
base.assert(serverinfo.channels[to], assert(serverinfo.channels[to],
"Received channel msg from unknown channel: " .. to) "Received channel msg from unknown channel: " .. to)
callback("channel_notice", serverinfo.channels[to], from, msg) callback("channel_notice", serverinfo.channels[to], from, msg)
else else
@ -297,7 +312,7 @@ end
-- on_quit {{{ -- on_quit {{{
function handlers.on_quit(from, quit_msg) function handlers.on_quit(from, quit_msg)
for name, chan in base.pairs(serverinfo.channels) do for name, chan in pairs(serverinfo.channels) do
chan:_remove_user(from) chan:_remove_user(from)
end end
callback("quit", from, quit_msg) callback("quit", from, quit_msg)
@ -307,7 +322,7 @@ end
-- on_ping {{{ -- on_ping {{{
-- respond to server pings to make sure it knows we are alive -- respond to server pings to make sure it knows we are alive
function handlers.on_ping(from, respond_to) function handlers.on_ping(from, respond_to)
send("PONG", respond_to) irc.send("PONG", respond_to)
end end
-- }}} -- }}}
-- }}} -- }}}
@ -316,7 +331,7 @@ end
-- on_rpl_topic {{{ -- on_rpl_topic {{{
-- catch topic changes -- catch topic changes
function handlers.on_rpl_topic(from, chan, topic) function handlers.on_rpl_topic(from, chan, topic)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received topic information about unknown channel: " .. chan) "Received topic information about unknown channel: " .. chan)
serverinfo.channels[chan]._topic.text = topic serverinfo.channels[chan]._topic.text = topic
end end
@ -324,7 +339,7 @@ end
-- on_rpl_notopic {{{ -- on_rpl_notopic {{{
function handlers.on_rpl_notopic(from, chan) function handlers.on_rpl_notopic(from, chan)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received topic information about unknown channel: " .. chan) "Received topic information about unknown channel: " .. chan)
serverinfo.channels[chan]._topic.text = "" serverinfo.channels[chan]._topic.text = ""
end end
@ -333,21 +348,21 @@ end
-- on_rpl_topicdate {{{ -- on_rpl_topicdate {{{
-- "topic was set by <user> at <time>" -- "topic was set by <user> at <time>"
function handlers.on_rpl_topicdate(from, chan, user, time) function handlers.on_rpl_topicdate(from, chan, user, time)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received topic information about unknown channel: " .. chan) "Received topic information about unknown channel: " .. chan)
serverinfo.channels[chan]._topic.user = user serverinfo.channels[chan]._topic.user = user
serverinfo.channels[chan]._topic.time = base.tonumber(time) serverinfo.channels[chan]._topic.time = tonumber(time)
end end
-- }}} -- }}}
-- on_rpl_namreply {{{ -- on_rpl_namreply {{{
-- handles a NAMES reply -- handles a NAMES reply
function handlers.on_rpl_namreply(from, chanmode, chan, userlist) function handlers.on_rpl_namreply(from, chanmode, chan, userlist)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received user information about unknown channel: " .. chan) "Received user information about unknown channel: " .. chan)
serverinfo.channels[chan]._chanmode = constants.chanmodes[chanmode] 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 for k,v in ipairs(users) do
if v:sub(1, 1) == "@" or v:sub(1, 1) == "+" then if v:sub(1, 1) == "@" or v:sub(1, 1) == "+" then
local nick = v:sub(2) 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))
@ -362,7 +377,7 @@ end
-- when we get this message, the channel join has completed, so call the -- when we get this message, the channel join has completed, so call the
-- external cb -- external cb
function handlers.on_rpl_endofnames(from, chan) function handlers.on_rpl_endofnames(from, chan)
base.assert(serverinfo.channels[chan], assert(serverinfo.channels[chan],
"Received user information about unknown channel: " .. chan) "Received user information about unknown channel: " .. chan)
if not serverinfo.channels[chan].join_complete then if not serverinfo.channels[chan].join_complete then
callback("me_join", serverinfo.channels[chan]) callback("me_join", serverinfo.channels[chan])
@ -425,7 +440,7 @@ function handlers.on_rpl_whoischannels(from, nick, channel_list)
if not requestinfo.whois[nick].channels then if not requestinfo.whois[nick].channels then
requestinfo.whois[nick].channels = {} requestinfo.whois[nick].channels = {}
end end
for _, channel in base.ipairs(misc._split(channel_list)) do for _, channel in ipairs(misc._split(channel_list)) do
table.insert(requestinfo.whois[nick].channels, channel) table.insert(requestinfo.whois[nick].channels, channel)
end end
end end
@ -466,7 +481,7 @@ function handlers.on_rpl_endofwhois(from, nick)
local cb = table.remove(icallbacks.whois[nick], 1) local cb = table.remove(icallbacks.whois[nick], 1)
cb(requestinfo.whois[nick]) cb(requestinfo.whois[nick])
requestinfo.whois[nick] = nil requestinfo.whois[nick] = nil
if #icallbacks.whois[nick] > 0 then send("WHOIS", nick) if #icallbacks.whois[nick] > 0 then irc.send("WHOIS", nick)
else icallbacks.whois[nick] = nil else icallbacks.whois[nick] = nil
end end
end end
@ -476,7 +491,7 @@ end
function handlers.on_rpl_version(from, version, server, comments) function handlers.on_rpl_version(from, version, server, comments)
local cb = table.remove(icallbacks.serverversion[server], 1) local cb = table.remove(icallbacks.serverversion[server], 1)
cb({version = version, server = server, comments = comments}) cb({version = version, server = server, comments = comments})
if #icallbacks.serverversion[server] > 0 then send("VERSION", server) if #icallbacks.serverversion[server] > 0 then irc.send("VERSION", server)
else icallbacks.serverversion[server] = nil else icallbacks.serverversion[server] = nil
end end
end end
@ -486,7 +501,7 @@ end
function on_rpl_time(from, server, time) function on_rpl_time(from, server, time)
local cb = table.remove(icallbacks.servertime[server], 1) local cb = table.remove(icallbacks.servertime[server], 1)
cb({time = time, server = server}) cb({time = time, server = server})
if #icallbacks.servertime[server] > 0 then send("TIME", server) if #icallbacks.servertime[server] > 0 then irc.send("TIME", server)
else icallbacks.servertime[server] = nil else icallbacks.servertime[server] = nil
end end
end end
@ -498,7 +513,7 @@ end
-- on_action {{{ -- on_action {{{
function ctcp_handlers.on_action(from, to, message) function ctcp_handlers.on_action(from, to, message)
if to:sub(1, 1) == "#" then if to:sub(1, 1) == "#" then
base.assert(serverinfo.channels[to], assert(serverinfo.channels[to],
"Received channel msg from unknown channel: " .. to) "Received channel msg from unknown channel: " .. to)
callback("channel_act", serverinfo.channels[to], from, message) callback("channel_act", serverinfo.channels[to], from, message)
else else
@ -511,7 +526,7 @@ end
-- TODO: can we not have this handler be registered unless the dcc module is -- TODO: can we not have this handler be registered unless the dcc module is
-- loaded? -- loaded?
function ctcp_handlers.on_dcc(from, to, message) 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 = table.unpack(misc._split(message, " ", nil, '"', '"'))
address = misc._ip_int_to_str(address) address = misc._ip_int_to_str(address)
if type == "SEND" then if type == "SEND" then
if callback("dcc_send", from, to, argument, address, port, size) then if callback("dcc_send", from, to, argument, address, port, size) then
@ -525,25 +540,25 @@ end
-- on_version {{{ -- on_version {{{
function ctcp_handlers.on_version(from, to) function ctcp_handlers.on_version(from, to)
notice(from, c("VERSION", _VERSION .. " running under " .. base._VERSION .. " with " .. socket._VERSION)) irc.notice(from, c("VERSION", irc._VERSION .. " running under " .. _VERSION .. " with " .. socket._VERSION))
end end
-- }}} -- }}}
-- on_errmsg {{{ -- on_errmsg {{{
function ctcp_handlers.on_errmsg(from, to, message) function ctcp_handlers.on_errmsg(from, to, message)
notice(from, c("ERRMSG", message, ":No error has occurred")) irc.notice(from, c("ERRMSG", message, ":No error has occurred"))
end end
-- }}} -- }}}
-- on_ping {{{ -- on_ping {{{
function ctcp_handlers.on_ping(from, to, timestamp) function ctcp_handlers.on_ping(from, to, timestamp)
notice(from, c("PING", timestamp)) irc.notice(from, c("PING", timestamp))
end end
-- }}} -- }}}
-- on_time {{{ -- on_time {{{
function ctcp_handlers.on_time(from, to) function ctcp_handlers.on_time(from, to)
notice(from, c("TIME", os.date())) irc.notice(from, c("TIME", os.date()))
end end
-- }}} -- }}}
-- }}} -- }}}
@ -559,7 +574,7 @@ function ctcp_handlers.on_rpl_version(from, to, version)
local lfrom = from:lower() local lfrom = from:lower()
local cb = table.remove(icallbacks.ctcp_version[lfrom], 1) local cb = table.remove(icallbacks.ctcp_version[lfrom], 1)
cb({version = version, nick = from}) cb({version = version, nick = from})
if #icallbacks.ctcp_version[lfrom] > 0 then say(from, c("VERSION")) if #icallbacks.ctcp_version[lfrom] > 0 then irc.say(from, c("VERSION"))
else icallbacks.ctcp_version[lfrom] = nil else icallbacks.ctcp_version[lfrom] = nil
end end
end end
@ -576,7 +591,7 @@ function ctcp_handlers.on_rpl_ping(from, to, timestamp)
local lfrom = from:lower() local lfrom = from:lower()
local cb = table.remove(icallbacks.ctcp_ping[lfrom], 1) local cb = table.remove(icallbacks.ctcp_ping[lfrom], 1)
cb({time = os.time() - timestamp, nick = from}) cb({time = os.time() - timestamp, nick = from})
if #icallbacks.ctcp_ping[lfrom] > 0 then say(from, c("PING", os.time())) if #icallbacks.ctcp_ping[lfrom] > 0 then irc.say(from, c("PING", os.time()))
else icallbacks.ctcp_ping[lfrom] = nil else icallbacks.ctcp_ping[lfrom] = nil
end end
end end
@ -587,7 +602,7 @@ function ctcp_handlers.on_rpl_time(from, to, time)
local lfrom = from:lower() local lfrom = from:lower()
local cb = table.remove(icallbacks.ctcp_time[lfrom], 1) local cb = table.remove(icallbacks.ctcp_time[lfrom], 1)
cb({time = time, nick = from}) cb({time = time, nick = from})
if #icallbacks.ctcp_time[lfrom] > 0 then say(from, c("TIME")) if #icallbacks.ctcp_time[lfrom] > 0 then irc.say(from, c("TIME"))
else icallbacks.ctcp_time[lfrom] = nil else icallbacks.ctcp_time[lfrom] = nil
end end
end end
@ -605,7 +620,7 @@ end
-- @param mode 'r' if the socket is for reading, 'w' if for writing -- @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. -- @param cb Callback to call when the socket is ready for reading/writing.
-- It will be called with the socket as the single argument. -- It will be called with the socket as the single argument.
function _register_socket(sock, mode, cb) function irc._register_socket(sock, mode, cb)
local socks, cbs local socks, cbs
if mode == 'r' then if mode == 'r' then
socks = rsockets socks = rsockets
@ -614,7 +629,7 @@ function _register_socket(sock, mode, cb)
socks = wsockets socks = wsockets
cbs = wcallbacks cbs = wcallbacks
end end
base.assert(not cbs[sock], "socket already registered") assert(not cbs[sock], "socket already registered")
table.insert(socks, sock) table.insert(socks, sock)
cbs[sock] = cb cbs[sock] = cb
end end
@ -625,7 +640,7 @@ end
-- Remove a previously registered socket. -- Remove a previously registered socket.
-- @param sock Socket to unregister -- @param sock Socket to unregister
-- @param mode 'r' to unregister it for reading, 'w' for writing -- @param mode 'r' to unregister it for reading, 'w' for writing
function _unregister_socket(sock, mode) function irc._unregister_socket(sock, mode)
local socks, cbs local socks, cbs
if mode == 'r' then if mode == 'r' then
socks = rsockets socks = rsockets
@ -634,7 +649,7 @@ function _unregister_socket(sock, mode)
socks = wsockets socks = wsockets
cbs = wcallbacks cbs = wcallbacks
end end
for i, v in base.ipairs(socks) do for i, v in ipairs(socks) do
if v == sock then table.remove(socks, i); break; end if v == sock then table.remove(socks, i); break; end
end end
cbs[sock] = nil cbs[sock] = nil
@ -670,22 +685,22 @@ end
-- dropping an idle connection -- dropping an idle connection
-- (default: '60')</li> -- (default: '60')</li>
-- </ul> -- </ul>
function connect(args) function irc.connect(args)
local network = args.network or NETWORK local network = args.network or irc.NETWORK
local port = args.port or PORT local port = args.port or irc.PORT
local nick = args.nick or NICK local nick = args.nick or irc.NICK
local username = args.username or USERNAME local username = args.username or irc.USERNAME
local realname = args.realname or REALNAME local realname = args.realname or irc.REALNAME
local timeout = args.timeout or TIMEOUT local timeout = args.timeout or irc.TIMEOUT
serverinfo.connecting = true serverinfo.connecting = true
if OUTFILE then irc_debug.set_output(OUTFILE) end if irc.OUTFILE then irc_debug.set_output(irc.OUTFILE) end
if DEBUG then irc_debug.enable() end if irc.DEBUG then irc_debug.enable() end
irc_sock = base.assert(socket.connect(network, port)) irc_sock = assert(socket.connect(network, port))
irc_sock:settimeout(timeout) irc_sock:settimeout(timeout)
_register_socket(irc_sock, 'r', incoming_message) irc._register_socket(irc_sock, 'r', incoming_message)
if args.pass then send("PASS", args.pass) end if args.pass then irc.send("PASS", args.pass) end
send("NICK", nick) irc.send("NICK", nick)
send("USER", username, get_ip(), network, realname) irc.send("USER", username, irc.get_ip(), network, realname)
begin_main_loop() begin_main_loop()
end end
-- }}} -- }}}
@ -694,9 +709,9 @@ end
--- ---
-- Close the connection to the irc server. -- Close the connection to the irc server.
-- @param message Quit message (optional, defaults to 'Leaving') -- @param message Quit message (optional, defaults to 'Leaving')
function quit(message) function irc.quit(message)
message = message or "Leaving" message = message or "Leaving"
send("QUIT", message) irc.send("QUIT", message)
serverinfo.connected = false serverinfo.connected = false
end end
-- }}} -- }}}
@ -705,10 +720,10 @@ end
--- ---
-- Join a channel. -- Join a channel.
-- @param channel Channel to join -- @param channel Channel to join
function join(channel) function irc.join(channel)
if not channel then return end if not channel then return end
serverinfo.channels[channel] = Channel.new(channel) serverinfo.channels[channel] = Channel.new(channel)
send("JOIN", channel) irc.send("JOIN", channel)
end end
-- }}} -- }}}
@ -716,10 +731,10 @@ end
--- ---
-- Leave a channel. -- Leave a channel.
-- @param channel Channel to leave -- @param channel Channel to leave
function part(channel) function irc.part(channel)
if not channel then return end if not channel then return end
serverinfo.channels[channel] = nil serverinfo.channels[channel] = nil
send("PART", channel) irc.send("PART", channel)
end end
-- }}} -- }}}
@ -728,10 +743,10 @@ end
-- Send a message to a user or channel. -- Send a message to a user or channel.
-- @param name User or channel to send the message to -- @param name User or channel to send the message to
-- @param message Message to send -- @param message Message to send
function say(name, message) function irc.say(name, message)
if not name then return end if not name then return end
message = message or "" message = message or ""
send("PRIVMSG", name, message) irc.send("PRIVMSG", name, message)
end end
-- }}} -- }}}
@ -740,10 +755,10 @@ end
-- Send a notice to a user or channel. -- Send a notice to a user or channel.
-- @param name User or channel to send the notice to -- @param name User or channel to send the notice to
-- @param message Message to send -- @param message Message to send
function notice(name, message) function irc.notice(name, message)
if not name then return end if not name then return end
message = message or "" message = message or ""
send("NOTICE", name, message) irc.send("NOTICE", name, message)
end end
-- }}} -- }}}
@ -752,10 +767,10 @@ end
-- Perform a /me action. -- Perform a /me action.
-- @param name User or channel to send the action to -- @param name User or channel to send the action to
-- @param action Action to send -- @param action Action to send
function act(name, action) function irc.act(name, action)
if not name then return end if not name then return end
action = action or "" action = action or ""
send("PRIVMSG", name, c("ACTION", action)) irc.send("PRIVMSG", name, c("ACTION", action))
end end
-- }}} -- }}}
-- }}} -- }}}
@ -771,13 +786,13 @@ end
-- <li><i>version:</i> the server version</li> -- <li><i>version:</i> the server version</li>
-- <li><i>comments:</i> other data provided by the server</li> -- <li><i>comments:</i> other data provided by the server</li>
-- </ul> -- </ul>
function server_version(cb) function irc.server_version(cb)
-- apparently the optional server parameter isn't supported for servers -- apparently the optional server parameter isn't supported for servers
-- which you are not directly connected to (freenode specific?) -- which you are not directly connected to (freenode specific?)
local server = serverinfo.host local server = serverinfo.host
if not icallbacks.serverversion[server] then if not icallbacks.serverversion[server] then
icallbacks.serverversion[server] = {cb} icallbacks.serverversion[server] = {cb}
send("VERSION", server) irc.send("VERSION", server)
else else
table.insert(icallbacks.serverversion[server], cb) table.insert(icallbacks.serverversion[server], cb)
end end
@ -808,12 +823,12 @@ end
-- joined</li> -- joined</li>
-- </ul> -- </ul>
-- @param nick User to request WHOIS information about -- @param nick User to request WHOIS information about
function whois(cb, nick) function irc.whois(cb, nick)
nick = nick:lower() nick = nick:lower()
requestinfo.whois[nick] = {} requestinfo.whois[nick] = {}
if not icallbacks.whois[nick] then if not icallbacks.whois[nick] then
icallbacks.whois[nick] = {cb} icallbacks.whois[nick] = {cb}
send("WHOIS", nick) irc.send("WHOIS", nick)
else else
table.insert(icallbacks.whois[nick], cb) table.insert(icallbacks.whois[nick], cb)
end end
@ -829,13 +844,13 @@ end
-- <li><i>server:</i> the server which responded to the request</li> -- <li><i>server:</i> the server which responded to the request</li>
-- <li><i>time:</i> the time reported by the server</li> -- <li><i>time:</i> the time reported by the server</li>
-- </ul> -- </ul>
function server_time(cb) function irc.server_time(cb)
-- apparently the optional server parameter isn't supported for servers -- apparently the optional server parameter isn't supported for servers
-- which you are not directly connected to (freenode specific?) -- which you are not directly connected to (freenode specific?)
local server = serverinfo.host local server = serverinfo.host
if not icallbacks.servertime[server] then if not icallbacks.servertime[server] then
icallbacks.servertime[server] = {cb} icallbacks.servertime[server] = {cb}
send("TIME", server) irc.send("TIME", server)
else else
table.insert(icallbacks.servertime[server], cb) table.insert(icallbacks.servertime[server], cb)
end end
@ -854,11 +869,11 @@ end
-- <li><i>time:</i> the roundtrip ping time, in seconds</li> -- <li><i>time:</i> the roundtrip ping time, in seconds</li>
-- </ul> -- </ul>
-- @param nick User to ping -- @param nick User to ping
function ctcp_ping(cb, nick) function irc.ctcp_ping(cb, nick)
nick = nick:lower() nick = nick:lower()
if not icallbacks.ctcp_ping[nick] then if not icallbacks.ctcp_ping[nick] then
icallbacks.ctcp_ping[nick] = {cb} icallbacks.ctcp_ping[nick] = {cb}
say(nick, c("PING", os.time())) irc.say(nick, c("PING", os.time()))
else else
table.insert(icallbacks.ctcp_ping[nick], cb) table.insert(icallbacks.ctcp_ping[nick], cb)
end end
@ -875,11 +890,11 @@ end
-- <li><i>time:</i> the localtime reported by the remote client</li> -- <li><i>time:</i> the localtime reported by the remote client</li>
-- </ul> -- </ul>
-- @param nick User to request the localtime from -- @param nick User to request the localtime from
function ctcp_time(cb, nick) function irc.ctcp_time(cb, nick)
nick = nick:lower() nick = nick:lower()
if not icallbacks.ctcp_time[nick] then if not icallbacks.ctcp_time[nick] then
icallbacks.ctcp_time[nick] = {cb} icallbacks.ctcp_time[nick] = {cb}
say(nick, c("TIME")) irc.say(nick, c("TIME"))
else else
table.insert(icallbacks.ctcp_time[nick], cb) table.insert(icallbacks.ctcp_time[nick], cb)
end end
@ -896,11 +911,11 @@ end
-- <li><i>version:</i> the version reported by the remote client</li> -- <li><i>version:</i> the version reported by the remote client</li>
-- </ul> -- </ul>
-- @param nick User to request the client version from -- @param nick User to request the client version from
function ctcp_version(cb, nick) function irc.ctcp_version(cb, nick)
nick = nick:lower() nick = nick:lower()
if not icallbacks.ctcp_version[nick] then if not icallbacks.ctcp_version[nick] then
icallbacks.ctcp_version[nick] = {cb} icallbacks.ctcp_version[nick] = {cb}
say(nick, c("VERSION")) irc.say(nick, c("VERSION"))
else else
table.insert(icallbacks.ctcp_version[nick], cb) table.insert(icallbacks.ctcp_version[nick], cb)
end end
@ -917,7 +932,7 @@ end
-- callback for this event -- callback for this event
-- @return Value of the original callback for this event (or nil if no previous -- @return Value of the original callback for this event (or nil if no previous
-- callback had been set) -- callback had been set)
function register_callback(name, fn) function irc.register_callback(name, fn)
local old_handler = user_handlers[name] local old_handler = user_handlers[name]
user_handlers[name] = fn user_handlers[name] = fn
return old_handler return old_handler
@ -936,10 +951,10 @@ end
-- an array. Strings are sent literally, arrays are CTCP quoted -- an array. Strings are sent literally, arrays are CTCP quoted
-- as a group. The last argument (if it exists) is preceded by -- as a group. The last argument (if it exists) is preceded by
-- a : (so it may contain spaces). -- a : (so it may contain spaces).
function send(command, ...) function irc.send(command, ...)
if not serverinfo.connected and not serverinfo.connecting then return end if not serverinfo.connected and not serverinfo.connecting then return end
local message = command local message = command
for i, v in base.ipairs({...}) do for i, v in ipairs({...}) do
if i == #{...} then if i == #{...} then
v = ":" .. v v = ":" .. v
end end
@ -958,7 +973,7 @@ end
-- Get the local IP address for the server connection. -- Get the local IP address for the server connection.
-- @return A string representation of the local IP address that the IRC server -- @return A string representation of the local IP address that the IRC server
-- connection is communicating on -- connection is communicating on
function get_ip() function irc.get_ip()
return (ip or irc_sock:getsockname()) return (ip or irc_sock:getsockname())
end end
-- }}} -- }}}
@ -967,7 +982,7 @@ end
--- ---
-- Set the local IP manually (to allow for NAT workarounds) -- Set the local IP manually (to allow for NAT workarounds)
-- @param new_ip IP address to set -- @param new_ip IP address to set
function set_ip(new_ip) function irc.set_ip(new_ip)
ip = new_ip ip = new_ip
end end
-- }}} -- }}}
@ -979,7 +994,7 @@ end
-- channels() is an iterator function for use in for loops. -- channels() is an iterator function for use in for loops.
-- For example, <pre>for chan in irc.channels() do print(chan:name) end</pre> -- For example, <pre>for chan in irc.channels() do print(chan:name) end</pre>
-- @see irc.channel -- @see irc.channel
function channels() function irc.channels()
return function(state, arg) return function(state, arg)
return misc._value_iter(state, arg, return misc._value_iter(state, arg,
function(v) function(v)
@ -992,3 +1007,5 @@ end
-- }}} -- }}}
-- }}} -- }}}
-- }}} -- }}}
return irc

View File

@ -2,17 +2,15 @@
-- Implementation of the Channel class -- Implementation of the Channel class
-- initialization {{{ -- initialization {{{
local base = _G local irc = libs.irc
local irc = require 'irc' local misc = libs.misc
local misc = require 'irc.misc' local socket = libs.socket
local socket = require 'socket'
local table = require 'table'
-- }}} -- }}}
--- ---
-- This module implements a channel object representing a single channel we -- This module implements a channel object representing a single channel we
-- have joined. -- have joined.
module 'irc.channel' local channel = {}
-- object metatable {{{ -- object metatable {{{
-- TODO: this <br /> shouldn't be necessary - bug in luadoc -- TODO: this <br /> shouldn't be necessary - bug in luadoc
@ -37,7 +35,7 @@ local mt = {
elseif key == "chanmode" then elseif key == "chanmode" then
return self._chanmode return self._chanmode
else else
return _M[key] return channel[key]
end end
end, end,
-- }}} -- }}}
@ -50,7 +48,7 @@ local mt = {
elseif key == "chanmode" then elseif key == "chanmode" then
return return
else else
base.rawset(self, key, value) rawset(self, key, value)
end end
end, end,
-- }}} -- }}}
@ -58,12 +56,12 @@ local mt = {
__concat = function(first, second) __concat = function(first, second)
local first_str, second_str local first_str, second_str
if base.type(first) == "table" then if type(first) == "table" then
first_str = first._name first_str = first._name
else else
first_str = first first_str = first
end end
if base.type(second) == "table" then if type(second) == "table" then
second_str = second._name second_str = second._name
else else
second_str = second second_str = second
@ -88,7 +86,7 @@ local mt = {
-- @param self Channel object -- @param self Channel object
-- @param set True to set the mode, false to unset it -- @param set True to set the mode, false to unset it
-- @param letter Letter of the mode -- @param letter Letter of the mode
local function set_basic_mode(self, set, letter) local function set_basic_mode(set, letter)
if set then if set then
irc.send("MODE", self.name, "+" .. letter) irc.send("MODE", self.name, "+" .. letter)
else else
@ -107,7 +105,7 @@ end
-- @param self Channel object -- @param self Channel object
-- @param user Nick of the user to add -- @param user Nick of the user to add
-- @param mode Mode (op/voice) of the user, in symbolic form (@/+) -- @param mode Mode (op/voice) of the user, in symbolic form (@/+)
function _add_user(self, user, mode) function channel:_add_user(user, mode)
mode = mode or '' mode = mode or ''
self._members[user] = mode .. user self._members[user] = mode .. user
end end
@ -118,7 +116,7 @@ end
-- Remove a user from the channel's internal user list. -- Remove a user from the channel's internal user list.
-- @param self Channel object -- @param self Channel object
-- @param user Nick of the user to remove -- @param user Nick of the user to remove
function _remove_user(self, user) function channel:_remove_user(user)
self._members[user] = nil self._members[user] = nil
end end
-- }}} -- }}}
@ -130,7 +128,7 @@ end
-- @param user Nick of the user to affect -- @param user Nick of the user to affect
-- @param on True if the mode is being set, false if it's being unset -- @param on True if the mode is being set, false if it's being unset
-- @param mode 'o' for op, 'v' for voice -- @param mode 'o' for op, 'v' for voice
function _change_status(self, user, on, mode) function channel:_change_status(user, on, mode)
if on then if on then
if mode == 'o' then if mode == 'o' then
self._members[user] = '@' .. user self._members[user] = '@' .. user
@ -152,7 +150,7 @@ end
-- @param self Channel object -- @param self Channel object
-- @param old_nick User's old nick -- @param old_nick User's old nick
-- @param new_nick User's new nick -- @param new_nick User's new nick
function _change_nick(self, old_nick, new_nick) function channel:_change_nick(old_nick, new_nick)
for member in self:each_member() do for member in self:each_member() do
local member_nick = member:gsub('@+', '') local member_nick = member:gsub('@+', '')
if member_nick == old_nick then if member_nick == old_nick then
@ -172,8 +170,8 @@ end
-- Creates a new Channel object. -- Creates a new Channel object.
-- @param chan Name of the new channel -- @param chan Name of the new channel
-- @return The new channel instance -- @return The new channel instance
function new(chan) function channel.new(chan)
return base.setmetatable({_name = chan, _topic = {}, _chanmode = "", return setmetatable({_name = chan, _topic = {}, _chanmode = "",
_members = {}}, mt) _members = {}}, mt)
end end
-- }}} -- }}}
@ -184,7 +182,7 @@ end
--- ---
-- Iterator over the ops in the channel -- Iterator over the ops in the channel
-- @param self Channel object -- @param self Channel object
function each_op(self) function channel:each_op()
return function(state, arg) return function(state, arg)
return misc._value_iter(state, arg, return misc._value_iter(state, arg,
function(v) function(v)
@ -200,7 +198,7 @@ end
--- ---
-- Iterator over the voiced users in the channel -- Iterator over the voiced users in the channel
-- @param self Channel object -- @param self Channel object
function each_voice(self) function channel:each_voice()
return function(state, arg) return function(state, arg)
return misc._value_iter(state, arg, return misc._value_iter(state, arg,
function(v) function(v)
@ -216,7 +214,7 @@ end
--- ---
-- Iterator over the normal users in the channel -- Iterator over the normal users in the channel
-- @param self Channel object -- @param self Channel object
function each_user(self) function channel:each_user()
return function(state, arg) return function(state, arg)
return misc._value_iter(state, arg, return misc._value_iter(state, arg,
function(v) function(v)
@ -233,7 +231,7 @@ end
--- ---
-- Iterator over all users in the channel -- Iterator over all users in the channel
-- @param self Channel object -- @param self Channel object
function each_member(self) function channel:each_member()
return misc._value_iter, self._members, nil return misc._value_iter, self._members, nil
end end
-- }}} -- }}}
@ -245,7 +243,7 @@ end
-- Gets an array of all the ops in the channel. -- Gets an array of all the ops in the channel.
-- @param self Channel object -- @param self Channel object
-- @return Array of channel ops -- @return Array of channel ops
function ops(self) function channel:ops()
local ret = {} local ret = {}
for nick in self:each_op() do for nick in self:each_op() do
table.insert(ret, nick) table.insert(ret, nick)
@ -259,7 +257,7 @@ end
-- Gets an array of all the voiced users in the channel. -- Gets an array of all the voiced users in the channel.
-- @param self Channel object -- @param self Channel object
-- @return Array of channel voiced users -- @return Array of channel voiced users
function voices(self) function channel:voices()
local ret = {} local ret = {}
for nick in self:each_voice() do for nick in self:each_voice() do
table.insert(ret, nick) table.insert(ret, nick)
@ -273,7 +271,7 @@ end
-- Gets an array of all the normal users in the channel. -- Gets an array of all the normal users in the channel.
-- @param self Channel object -- @param self Channel object
-- @return Array of channel normal users -- @return Array of channel normal users
function users(self) function channel:users()
local ret = {} local ret = {}
for nick in self:each_user() do for nick in self:each_user() do
table.insert(ret, nick) table.insert(ret, nick)
@ -287,7 +285,7 @@ end
-- Gets an array of all the users in the channel. -- Gets an array of all the users in the channel.
-- @param self Channel object -- @param self Channel object
-- @return Array of channel users -- @return Array of channel users
function members(self) function channel:members()
local ret = {} local ret = {}
-- not just returning self._members, since the return value shouldn't be -- not just returning self._members, since the return value shouldn't be
-- modifiable -- modifiable
@ -306,7 +304,7 @@ end
-- Ban a user from a channel. -- Ban a user from a channel.
-- @param self Channel object -- @param self Channel object
-- @param name User to ban -- @param name User to ban
function ban(self, name) function channel:ban(name)
irc.send("MODE", self.name, "+b", name) irc.send("MODE", self.name, "+b", name)
end end
-- }}} -- }}}
@ -317,7 +315,7 @@ end
-- Remove a ban on a user. -- Remove a ban on a user.
-- @param self Channel object -- @param self Channel object
-- @param name User to unban -- @param name User to unban
function unban(self, name) function channel:unban(name)
irc.send("MODE", self.name, "-b", name) irc.send("MODE", self.name, "-b", name)
end end
-- }}} -- }}}
@ -327,7 +325,7 @@ end
-- Give a user voice on a channel. -- Give a user voice on a channel.
-- @param self Channel object -- @param self Channel object
-- @param name User to give voice to -- @param name User to give voice to
function voice(self, name) function channel:voice(name)
irc.send("MODE", self.name, "+v", name) irc.send("MODE", self.name, "+v", name)
end end
-- }}} -- }}}
@ -337,7 +335,7 @@ end
-- Remove voice from a user. -- Remove voice from a user.
-- @param self Channel object -- @param self Channel object
-- @param name User to remove voice from -- @param name User to remove voice from
function devoice(self, name) function channel:devoice(name)
irc.send("MODE", self.name, "-v", name) irc.send("MODE", self.name, "-v", name)
end end
-- }}} -- }}}
@ -347,7 +345,7 @@ end
-- Give a user ops on a channel. -- Give a user ops on a channel.
-- @param self Channel object -- @param self Channel object
-- @param name User to op -- @param name User to op
function op(self, name) function channel:op(name)
irc.send("MODE", self.name, "+o", name) irc.send("MODE", self.name, "+o", name)
end end
-- }}} -- }}}
@ -357,7 +355,7 @@ end
-- Remove ops from a user. -- Remove ops from a user.
-- @param self Channel object -- @param self Channel object
-- @param name User to remove ops from -- @param name User to remove ops from
function deop(self, name) function channel:deop(name)
irc.send("MODE", self.name, "-o", name) irc.send("MODE", self.name, "-o", name)
end end
-- }}} -- }}}
@ -368,7 +366,7 @@ end
-- @param self Channel object -- @param self Channel object
-- @param new_limit New value for the channel limit (optional; limit is unset -- @param new_limit New value for the channel limit (optional; limit is unset
-- if this argument isn't passed) -- if this argument isn't passed)
function set_limit(self, new_limit) function channel:set_limit(new_limit)
if new_limit then if new_limit then
irc.send("MODE", self.name, "+l", new_limit) irc.send("MODE", self.name, "+l", new_limit)
else else
@ -383,7 +381,7 @@ end
-- @param self Channel object -- @param self Channel object
-- @param key New channel password (optional; password is unset if this -- @param key New channel password (optional; password is unset if this
-- argument isn't passed) -- argument isn't passed)
function set_key(self, key) function channel:set_key(key)
if key then if key then
irc.send("MODE", self.name, "+k", key) irc.send("MODE", self.name, "+k", key)
else else
@ -397,7 +395,7 @@ end
-- Set the private state of a channel. -- Set the private state of a channel.
-- @param self Channel object -- @param self Channel object
-- @param set True to set the channel as private, false to unset it -- @param set True to set the channel as private, false to unset it
function set_private(self, set) function channel:set_private(set)
set_basic_mode(self, set, "p") set_basic_mode(self, set, "p")
end end
-- }}} -- }}}
@ -407,7 +405,7 @@ end
-- Set the secret state of a channel. -- Set the secret state of a channel.
-- @param self Channel object -- @param self Channel object
-- @param set True to set the channel as secret, false to unset it -- @param set True to set the channel as secret, false to unset it
function set_secret(self, set) function channel:set_secret(set)
set_basic_mode(self, set, "s") set_basic_mode(self, set, "s")
end end
-- }}} -- }}}
@ -417,7 +415,7 @@ end
-- Set whether joining the channel requires an invite. -- Set whether joining the channel requires an invite.
-- @param self Channel object -- @param self Channel object
-- @param set True to set the channel invite only, false to unset it -- @param set True to set the channel invite only, false to unset it
function set_invite_only(self, set) function channel:set_invite_only(set)
set_basic_mode(self, set, "i") set_basic_mode(self, set, "i")
end end
-- }}} -- }}}
@ -427,7 +425,7 @@ end
-- If true, the topic can only be changed by an op. -- If true, the topic can only be changed by an op.
-- @param self Channel object -- @param self Channel object
-- @param set True to lock the topic, false to unlock it -- @param set True to lock the topic, false to unlock it
function set_topic_lock(self, set) function channel:set_topic_lock(set)
set_basic_mode(self, set, "t") set_basic_mode(self, set, "t")
end end
-- }}} -- }}}
@ -438,7 +436,7 @@ end
-- @param self Channel object -- @param self Channel object
-- @param set True to require users to be in the channel to send messages to -- @param set True to require users to be in the channel to send messages to
-- it, false to remove this restriction -- it, false to remove this restriction
function set_no_outside_messages(self, set) function channel:set_no_outside_messages(set)
set_basic_mode(self, set, "n") set_basic_mode(self, set, "n")
end end
-- }}} -- }}}
@ -448,7 +446,7 @@ end
-- Set whether voice is required to speak. -- Set whether voice is required to speak.
-- @param self Channel object -- @param self Channel object
-- @param set True to set the channel as moderated, false to unset it -- @param set True to set the channel as moderated, false to unset it
function set_moderated(self, set) function channel:set_moderated(set)
set_basic_mode(self, set, "m") set_basic_mode(self, set, "m")
end end
-- }}} -- }}}
@ -461,7 +459,7 @@ end
-- @param self Channel object -- @param self Channel object
-- @param nick Nick to search for -- @param nick Nick to search for
-- @return True if the nick is in the channel, false otherwise -- @return True if the nick is in the channel, false otherwise
function contains(self, nick) function channel:contains(nick)
for member in self:each_member() do for member in self:each_member() do
local member_nick = member:gsub('@+', '') local member_nick = member:gsub('@+', '')
if member_nick == nick then if member_nick == nick then
@ -473,3 +471,5 @@ end
-- }}} -- }}}
-- }}} -- }}}
-- }}} -- }}}
return channel

View File

@ -1,13 +1,13 @@
--- ---
-- This module holds various constants used by the IRC protocol. -- This module holds various constants used by the IRC protocol.
module "irc.constants" local constants = {}
-- protocol constants {{{ -- protocol constants {{{
IRC_MAX_MSG = 512 constants.IRC_MAX_MSG = 512
-- }}} -- }}}
-- server replies {{{ -- server replies {{{
replies = { constants.replies = {
-- Command responses {{{ -- Command responses {{{
[001] = "RPL_WELCOME", [001] = "RPL_WELCOME",
[002] = "RPL_YOURHOST", [002] = "RPL_YOURHOST",
@ -183,9 +183,11 @@ replies = {
-- }}} -- }}}
-- chanmodes {{{ -- chanmodes {{{
chanmodes = { constants.chanmodes = {
["@"] = "secret", ["@"] = "secret",
["*"] = "private", ["*"] = "private",
["="] = "public" ["="] = "public"
} }
-- }}} -- }}}
return constants

View File

@ -1,14 +1,10 @@
--- ---
-- Implementation of the CTCP protocol -- Implementation of the CTCP protocol
-- initialization {{{
local base = _G
local table = require "table"
-- }}}
--- ---
-- This module implements the various quoting and escaping requirements of the -- This module implements the various quoting and escaping requirements of the
-- CTCP protocol. -- CTCP protocol.
module "irc.ctcp" local ctcp = {}
-- internal functions {{{ -- internal functions {{{
-- _low_quote {{{ -- _low_quote {{{
@ -17,7 +13,7 @@ module "irc.ctcp"
-- to appear in an IRC packet). -- to appear in an IRC packet).
-- @param ... Strings to quote together, space separated -- @param ... Strings to quote together, space separated
-- @return Quoted string -- @return Quoted string
function _low_quote(...) function ctcp._low_quote(...)
local str = table.concat({...}, " ") local str = table.concat({...}, " ")
return str:gsub("[%z\n\r\020]", {["\000"] = "\0200", return str:gsub("[%z\n\r\020]", {["\000"] = "\0200",
["\n"] = "\020n", ["\n"] = "\020n",
@ -31,7 +27,7 @@ end
-- Removes low level quoting done by low_quote. -- Removes low level quoting done by low_quote.
-- @param str String with low level quoting applied to it -- @param str String with low level quoting applied to it
-- @return String with those quoting methods stripped off -- @return String with those quoting methods stripped off
function _low_dequote(str) function ctcp._low_dequote(str)
return str:gsub("\020(.?)", function(s) return str:gsub("\020(.?)", function(s)
if s == "0" then return "\000" end if s == "0" then return "\000" end
if s == "n" then return "\n" end if s == "n" then return "\n" end
@ -48,7 +44,7 @@ end
-- data (by the calling program). -- data (by the calling program).
-- @param ... Strings to apply CTCP quoting to together, space separated -- @param ... Strings to apply CTCP quoting to together, space separated
-- @return String with CTCP quoting applied -- @return String with CTCP quoting applied
function _ctcp_quote(...) function ctcp._ctcp_quote(...)
local str = table.concat({...}, " ") local str = table.concat({...}, " ")
local ret = str:gsub("[\001\\]", {["\001"] = "\\a", local ret = str:gsub("[\001\\]", {["\001"] = "\\a",
["\\"] = "\\\\"}) ["\\"] = "\\\\"})
@ -62,7 +58,7 @@ end
-- data (likely by ctcp_split). -- data (likely by ctcp_split).
-- @param str String with CTCP quoting -- @param str String with CTCP quoting
-- @return String with all CTCP quoting stripped -- @return String with all CTCP quoting stripped
function _ctcp_dequote(str) function ctcp._ctcp_dequote(str)
local ret = str:gsub("^\001", ""):gsub("\001$", "") local ret = str:gsub("^\001", ""):gsub("\001$", "")
return ret:gsub("\\(.?)", function(s) return ret:gsub("\\(.?)", function(s)
if s == "a" then return "\001" end if s == "a" then return "\001" end
@ -84,7 +80,7 @@ end
-- <li><i>ctcp:</i> True if the section was a CTCP message, false -- <li><i>ctcp:</i> True if the section was a CTCP message, false
-- otherwise</li> -- otherwise</li>
-- </ul> -- </ul>
function _ctcp_split(str) function ctcp._ctcp_split(str)
local ret = {} local ret = {}
local iter = 1 local iter = 1
while true do while true do
@ -103,7 +99,7 @@ function _ctcp_split(str)
end end
if not s then break end if not s then break end
if ctcp_string ~= "" then if ctcp_string ~= "" then
table.insert(ret, {str = _ctcp_dequote(ctcp_string), ctcp = true}) table.insert(ret, {str = ctcp._ctcp_dequote(ctcp_string), ctcp = true})
end end
iter = e + 1 iter = e + 1
@ -113,3 +109,5 @@ function _ctcp_split(str)
end end
-- }}} -- }}}
-- }}} -- }}}
return ctcp

View File

@ -1,26 +1,22 @@
--- ---
-- Implementation of the DCC protocol -- Implementation of the DCC protocol
-- initialization {{{ -- initialization {{{
local base = _G local irc = libs.irc
local irc = require 'irc' local ctcp = libs.ctcp
local ctcp = require 'irc.ctcp'
local c = ctcp._ctcp_quote local c = ctcp._ctcp_quote
local irc_debug = require 'irc.debug' local irc_debug = libs.debug
local misc = require 'irc.misc' local misc = libs.misc
local socket = require 'socket' local socket = libs.socket
local coroutine = require 'coroutine'
local io = require 'io'
local string = require 'string'
-- }}} -- }}}
--- ---
-- This module implements the DCC protocol. File transfers (DCC SEND) are -- This module implements the DCC protocol. File transfers (DCC SEND) are
-- handled, but DCC CHAT is not, as of yet. -- handled, but DCC CHAT is not, as of yet.
module 'irc.dcc' local dcc = {}
-- defaults {{{ -- defaults {{{
FIRST_PORT = 1028 dcc.FIRST_PORT = 1028
LAST_PORT = 5000 dcc.LAST_PORT = 5000
-- }}} -- }}}
-- private functions {{{ -- private functions {{{
@ -138,13 +134,13 @@ end
-- @param address IP address of the remote user in low level int form -- @param address IP address of the remote user in low level int form
-- @param port Port to connect to at the remote user -- @param port Port to connect to at the remote user
-- @param packet_size Size of the packets the remote user will be sending -- @param packet_size Size of the packets the remote user will be sending
function _accept(filename, address, port, packet_size) function dcc._accept(filename, address, port, packet_size)
debug_dcc("Accepting a DCC SEND request from " .. address .. ":" .. port) debug_dcc("Accepting a DCC SEND request from " .. address .. ":" .. port)
packet_size = packet_size or 1024 packet_size = packet_size or 1024
local sock = base.assert(socket.tcp()) local sock = assert(socket.tcp())
base.assert(sock:connect(address, port)) assert(sock:connect(address, port))
sock:settimeout(0.1) sock:settimeout(0.1)
local file = base.assert(io.open(misc._get_unique_filename(filename), "w")) local file = assert(io.open(misc._get_unique_filename(filename), "w"))
irc._register_socket(sock, 'r', irc._register_socket(sock, 'r',
coroutine.wrap(function(s) coroutine.wrap(function(s)
return accept_file(s, file, packet_size) return accept_file(s, file, packet_size)
@ -162,17 +158,17 @@ end
-- @param port Port to accept connections on (optional, defaults to -- @param port Port to accept connections on (optional, defaults to
-- choosing an available port between FIRST_PORT and LAST_PORT -- choosing an available port between FIRST_PORT and LAST_PORT
-- above) -- above)
function send(nick, filename, port) function dcc.send(nick, filename, port)
port = port or FIRST_PORT port = port or dcc.FIRST_PORT
local sock local sock
repeat repeat
sock = base.assert(socket.tcp()) sock = assert(socket.tcp())
err, msg = sock:bind('*', port) err, msg = sock:bind('*', port)
port = port + 1 port = port + 1
until msg ~= "address already in use" and port <= LAST_PORT + 1 until msg ~= "address already in use" and port <= dcc.LAST_PORT + 1
port = port - 1 port = port - 1
base.assert(err, msg) assert(err, msg)
base.assert(sock:listen(1)) assert(sock:listen(1))
local ip = misc._ip_str_to_int(irc.get_ip()) local ip = misc._ip_str_to_int(irc.get_ip())
local file, err = io.open(filename) local file, err = io.open(filename)
if not file then if not file then
@ -194,3 +190,5 @@ function send(nick, filename, port)
end end
-- }}} -- }}}
-- }}} -- }}}
return dcc

View File

@ -1,17 +1,13 @@
--- ---
-- Basic debug output -- Basic debug output
-- initialization {{{
local base = _G
local io = require 'io'
-- }}}
--- ---
-- This module implements a few useful debug functions for use throughout the -- This module implements a few useful debug functions for use throughout the
-- rest of the code. -- rest of the code.
module 'irc.debug' local irc_debug = {}
-- defaults {{{ -- defaults {{{
COLOR = true irc_debug.COLOR = true
-- }}} -- }}}
-- local variables {{{ -- local variables {{{
@ -27,10 +23,10 @@ local outfile = io.output()
-- @param msg Message text -- @param msg Message text
-- @param color Which terminal code to use for color output (defaults to -- @param color Which terminal code to use for color output (defaults to
-- dark gray) -- dark gray)
function _message(msg_type, msg, color) function irc_debug._message(msg_type, msg, color)
if ON then if ON then
local endcolor = "" local endcolor = ""
if COLOR and outfile == io.stdout then if irc_debug.COLOR and outfile == io.stdout then
color = color or "\027[1;30m" color = color or "\027[1;30m"
endcolor = "\027[0m" endcolor = "\027[0m"
else else
@ -48,9 +44,9 @@ end
-- error(). -- error().
-- @param msg Error message -- @param msg Error message
-- @see error -- @see error
function _err(msg) function irc_debug._err(msg)
_message("ERR", msg, "\027[0;31m") irc_debug._message("ERR", msg, "\027[0;31m")
base.error(msg, 2) error(msg, 2)
end end
-- }}} -- }}}
@ -58,8 +54,8 @@ end
-- --
-- Signal a warning. Writes the warning message to the screen in yellow. -- Signal a warning. Writes the warning message to the screen in yellow.
-- @param msg Warning message -- @param msg Warning message
function _warn(msg) function irc_debug._warn(msg)
_message("WARN", msg, "\027[0;33m") irc_debug._message("WARN", msg, "\027[0;33m")
end end
-- }}} -- }}}
-- }}} -- }}}
@ -68,7 +64,7 @@ end
-- enable {{{ -- enable {{{
--- ---
-- Turns on debug output. -- Turns on debug output.
function enable() function irc_debug.enable()
ON = true ON = true
end end
-- }}} -- }}}
@ -76,7 +72,7 @@ end
-- disable {{{ -- disable {{{
--- ---
-- Turns off debug output. -- Turns off debug output.
function disable() function irc_debug.disable()
ON = false ON = false
end end
-- }}} -- }}}
@ -85,8 +81,10 @@ end
--- ---
-- Redirects output to a file rather than stdout. -- Redirects output to a file rather than stdout.
-- @param file File to write debug output to -- @param file File to write debug output to
function set_output(file) function irc_debug.set_output(file)
outfile = base.assert(io.open(file)) outfile = assert(io.open(file))
end end
-- }}} -- }}}
-- }}} -- }}}
return irc_debug

View File

@ -1,19 +1,16 @@
--- ---
-- Implementation of IRC server message parsing -- Implementation of IRC server message parsing
-- initialization {{{ -- initialization {{{
local base = _G local constants = libs.constants
local constants = require 'irc.constants' local ctcp = libs.ctcp
local ctcp = require 'irc.ctcp' local irc_debug = libs.debug
local irc_debug = require 'irc.debug' local misc = libs.misc
local misc = require 'irc.misc' local socket = libs.socket
local socket = require 'socket'
local string = require 'string'
local table = require 'table'
-- }}} -- }}}
--- ---
-- This module contains parsing functions for IRC server messages. -- This module contains parsing functions for IRC server messages.
module 'irc.message' local message = {}
-- internal functions {{{ -- internal functions {{{
-- _parse {{{ -- _parse {{{
@ -32,7 +29,7 @@ module 'irc.message'
-- to the received command</li> -- to the received command</li>
-- --
-- </ul> -- </ul>
function _parse(str) function message._parse(str)
-- low-level ctcp quoting {{{ -- low-level ctcp quoting {{{
str = ctcp._low_dequote(str) str = ctcp._low_dequote(str)
-- }}} -- }}}
@ -49,8 +46,8 @@ function _parse(str)
local reply = false local reply = false
if command:find("^%d%d%d$") then if command:find("^%d%d%d$") then
reply = true reply = true
if constants.replies[base.tonumber(command)] then if constants.replies[tonumber(command)] then
command = constants.replies[base.tonumber(command)] command = constants.replies[tonumber(command)]
else else
irc_debug._warn("Unknown server reply: " .. command) irc_debug._warn("Unknown server reply: " .. command)
end end
@ -67,3 +64,5 @@ function _parse(str)
end end
-- }}} -- }}}
-- }}} -- }}}
return message

View File

@ -1,25 +1,20 @@
--- ---
-- Various useful functions that didn't fit anywhere else -- Various useful functions that didn't fit anywhere else
-- initialization {{{ -- initialization {{{
local base = _G local irc_debug = libs.debug
local irc_debug = require 'irc.debug' local socket = libs.socket
local socket = require 'socket'
local math = require 'math'
local os = require 'os'
local string = require 'string'
local table = require 'table'
-- }}} -- }}}
--- ---
-- This module contains various useful functions which didn't fit in any of the -- This module contains various useful functions which didn't fit in any of the
-- other modules. -- other modules.
module 'irc.misc' local misc = {}
-- defaults {{{ -- defaults {{{
DELIM = ' ' misc.DELIM = ' '
PATH_SEP = '/' misc.PATH_SEP = '/'
ENDIANNESS = "big" misc.ENDIANNESS = "big"
INT_BYTES = 4 misc.INT_BYTES = 4
-- }}} -- }}}
-- private functions {{{ -- private functions {{{
@ -49,9 +44,9 @@ end
-- in str will be considered one substring) -- in str will be considered one substring)
-- @param rquotes String of characters to use as closing quotes -- @param rquotes String of characters to use as closing quotes
-- @return Array of strings, one for each substring that was separated out -- @return Array of strings, one for each substring that was separated out
function _split(str, delim, end_delim, lquotes, rquotes) function misc._split(str, delim, end_delim, lquotes, rquotes)
-- handle arguments {{{ -- handle arguments {{{
delim = "["..(delim or DELIM).."]" delim = "["..(delim or misc.DELIM).."]"
if end_delim then end_delim = "["..end_delim.."]" end if end_delim then end_delim = "["..end_delim.."]" end
if lquotes then lquotes = "["..lquotes.."]" end if lquotes then lquotes = "["..lquotes.."]" end
if rquotes then rquotes = "["..rquotes.."]" end if rquotes then rquotes = "["..rquotes.."]" end
@ -111,8 +106,8 @@ end
-- @param path Path to the file -- @param path Path to the file
-- @param sep Directory separator (optional, defaults to PATH_SEP) -- @param sep Directory separator (optional, defaults to PATH_SEP)
-- @return The basename of the file -- @return The basename of the file
function _basename(path, sep) function misc._basename(path, sep)
sep = sep or PATH_SEP sep = sep or misc.PATH_SEP
if not path:find(sep) then return path end if not path:find(sep) then return path end
return socket.skip(2, path:find(".*" .. sep .. "(.*)")) return socket.skip(2, path:find(".*" .. sep .. "(.*)"))
end end
@ -124,8 +119,8 @@ end
-- @param path Path to the file -- @param path Path to the file
-- @param sep Directory separator (optional, defaults to PATH_SEP) -- @param sep Directory separator (optional, defaults to PATH_SEP)
-- @return The dirname of the file -- @return The dirname of the file
function _dirname(path, sep) function misc._dirname(path, sep)
sep = sep or PATH_SEP sep = sep or misc.PATH_SEP
if not path:find(sep) then return "." end if not path:find(sep) then return "." end
return socket.skip(2, path:find("(.*)" .. sep .. ".*")) return socket.skip(2, path:find("(.*)" .. sep .. ".*"))
end end
@ -139,9 +134,9 @@ end
-- @param endian Which endianness to use (big, little, host, network) (defaultsi -- @param endian Which endianness to use (big, little, host, network) (defaultsi
-- to ENDIANNESS) -- to ENDIANNESS)
-- @return A string whose first INT_BYTES characters make a low-level int -- @return A string whose first INT_BYTES characters make a low-level int
function _str_to_int(str, bytes, endian) function misc._str_to_int(str, bytes, endian)
bytes = bytes or INT_BYTES bytes = bytes or misc.INT_BYTES
endian = endian or ENDIANNESS endian = endian or misc.ENDIANNESS
local ret = "" local ret = ""
for i = 0, bytes - 1 do for i = 0, bytes - 1 do
local new_byte = string.char(math.fmod(str / (2^(8 * i)), 256)) local new_byte = string.char(math.fmod(str / (2^(8 * i)), 256))
@ -159,8 +154,8 @@ end
-- @param int String whose bytes correspond to the bytes of a low-level int -- @param int String whose bytes correspond to the bytes of a low-level int
-- @param endian Endianness of the int argument (defaults to ENDIANNESS) -- @param endian Endianness of the int argument (defaults to ENDIANNESS)
-- @return String representation of the low-level int argument -- @return String representation of the low-level int argument
function _int_to_str(int, endian) function misc._int_to_str(int, endian)
endian = endian or ENDIANNESS endian = endian or misc.ENDIANNESS
local ret = 0 local ret = 0
for i = 1, int:len() do for i = 1, int:len() do
if endian == "big" or endian == "network" then ind = int:len() - i + 1 if endian == "big" or endian == "network" then ind = int:len() - i + 1
@ -178,7 +173,7 @@ end
-- Converts a string IP address to a low-level int. -- Converts a string IP address to a low-level int.
-- @param ip_str String representation of an IP address -- @param ip_str String representation of an IP address
-- @return Low-level int representation of that IP address -- @return Low-level int representation of that IP address
function _ip_str_to_int(ip_str) function misc._ip_str_to_int(ip_str)
local i = 3 local i = 3
local ret = 0 local ret = 0
for num in ip_str:gmatch("%d+") do for num in ip_str:gmatch("%d+") do
@ -195,7 +190,7 @@ end
-- Converts an int to a string IP address. -- Converts an int to a string IP address.
-- @param ip_int Low-level int representation of an IP address -- @param ip_int Low-level int representation of an IP address
-- @return String representation of that IP address -- @return String representation of that IP address
function _ip_int_to_str(ip_int) function misc._ip_int_to_str(ip_int)
local ip = {} local ip = {}
for i = 3, 0, -1 do for i = 3, 0, -1 do
local new_num = math.floor(ip_int / 2^(i * 8)) local new_num = math.floor(ip_int / 2^(i * 8))
@ -212,7 +207,7 @@ end
-- @param filename Filename to start with -- @param filename Filename to start with
-- @return Filename (same as the one we started with, except possibly with some -- @return Filename (same as the one we started with, except possibly with some
-- numbers appended) which does not currently exist on the filesystem -- numbers appended) which does not currently exist on the filesystem
function _get_unique_filename(filename) function misc._get_unique_filename(filename)
if not exists(filename) then return filename end if not exists(filename) then return filename end
local count = 1 local count = 1
@ -231,8 +226,8 @@ end
-- @param fn Function to try to call -- @param fn Function to try to call
-- @param ... Arguments to fn -- @param ... Arguments to fn
-- @return The return values of fn, if it was successfully called -- @return The return values of fn, if it was successfully called
function _try_call(fn, ...) function misc._try_call(fn, ...)
if base.type(fn) == "function" then if type(fn) == "function" then
return fn(...) return fn(...)
end end
end end
@ -245,8 +240,8 @@ end
-- @param fn Function to try to call -- @param fn Function to try to call
-- @param ... Arguments to fn -- @param ... Arguments to fn
-- @return The return values of fn, if it was successfully called -- @return The return values of fn, if it was successfully called
function _try_call_warn(msg, fn, ...) function misc._try_call_warn(msg, fn, ...)
if base.type(fn) == "function" then if type(fn) == "function" then
return fn(...) return fn(...)
else else
irc_debug._warn(msg) irc_debug._warn(msg)
@ -257,16 +252,16 @@ end
-- _value_iter {{{ -- _value_iter {{{
-- --
-- Iterator to iterate over just the values of a table. -- Iterator to iterate over just the values of a table.
function _value_iter(state, arg, pred) function misc._value_iter(state, arg, pred)
for k, v in base.pairs(state) do for k, v in pairs(state) do
if arg == v then arg = k end if arg == v then arg = k end
end end
local key, val = base.next(state, arg) local key, val = next(state, arg)
if not key then return end if not key then return end
if base.type(pred) == "function" then if type(pred) == "function" then
while not pred(val) do while not pred(val) do
key, val = base.next(state, key) key, val = next(state, key)
if not key then return end if not key then return end
end end
end end
@ -281,7 +276,7 @@ end
-- @return nick -- @return nick
-- @return username (if it exists) -- @return username (if it exists)
-- @return hostname (if it exists) -- @return hostname (if it exists)
function _parse_user(user) function misc._parse_user(user)
local found, bang, nick = user:find("^([^!]*)!") local found, bang, nick = user:find("^([^!]*)!")
if found then if found then
user = user:sub(bang + 1) user = user:sub(bang + 1)
@ -301,3 +296,5 @@ function _parse_user(user)
end end
-- }}} -- }}}
-- }}} -- }}}
return misc

View File

@ -225,4 +225,4 @@ local function on_dcc_send()
end end
irc.register_callback("dcc_send", on_dcc_send) irc.register_callback("dcc_send", on_dcc_send)
irc.connect{network = "irc.freenode.net", nick = "doylua"} irc.connect{network = "irc.libera.chat", nick = "doylua"}