|
|
|
@ -25,6 +25,7 @@ itte.config = {
|
|
|
|
|
debug = false,
|
|
|
|
|
admin_handlers = { "connect", "quit", "reload", "servers", "join", "part" },
|
|
|
|
|
task_handler_prefix = "th_",
|
|
|
|
|
target_handler = "target",
|
|
|
|
|
messages = {
|
|
|
|
|
connect_success = "Connected to {{server}}.",
|
|
|
|
|
help = "Service codes available: {{codes}}",
|
|
|
|
@ -51,9 +52,10 @@ itte.config = {
|
|
|
|
|
dc_svr = { "conn", "Connection to {{server}} closed." },
|
|
|
|
|
exit = { "conn", "Exiting ..." },
|
|
|
|
|
listen = { "listen" },
|
|
|
|
|
names = { "names", "channel", "users" },
|
|
|
|
|
nickserv = { "auth_nickserv" },
|
|
|
|
|
privmsg = { "sender", "recipient", "reply_to", "code", "code_params",
|
|
|
|
|
"body" },
|
|
|
|
|
privmsg = { "privmsg", "sender", "recipient", "reply_to", "code",
|
|
|
|
|
"code_params", "body" },
|
|
|
|
|
redact = { "********" },
|
|
|
|
|
send = { "send" },
|
|
|
|
|
svrs_not_found = { "config", "Error: servers not found." },
|
|
|
|
@ -217,6 +219,11 @@ function itte.get_commands(svr)
|
|
|
|
|
check = "MODE",
|
|
|
|
|
resp = "JOIN ",
|
|
|
|
|
},
|
|
|
|
|
names = {
|
|
|
|
|
-- Check and response are reversed
|
|
|
|
|
check = "353",
|
|
|
|
|
resp = "NAMES",
|
|
|
|
|
},
|
|
|
|
|
nick = {
|
|
|
|
|
check = "NOTICE",
|
|
|
|
|
resp = "NICK " .. svr.nick,
|
|
|
|
@ -243,6 +250,18 @@ function itte.get_commands(svr)
|
|
|
|
|
check = nil,
|
|
|
|
|
resp = "QUIT :",
|
|
|
|
|
},
|
|
|
|
|
target_private = {
|
|
|
|
|
-- Like privmsg but targets bot nick in a direct message
|
|
|
|
|
-- Escape magic character "-"
|
|
|
|
|
check = "PRIVMSG(.*)" .. string.gsub(svr.nick, "-", "%%-") .. ":",
|
|
|
|
|
resp = "PRIVMSG ",
|
|
|
|
|
},
|
|
|
|
|
target_public = {
|
|
|
|
|
-- Like privmsg but targets bot nick in a channel
|
|
|
|
|
-- Escape magic character "-"
|
|
|
|
|
check = "PRIVMSG(.*)#(.*):" .. string.gsub(svr.nick, "-", "%%-"),
|
|
|
|
|
resp = "PRIVMSG ",
|
|
|
|
|
},
|
|
|
|
|
user = {
|
|
|
|
|
check = "NOTICE",
|
|
|
|
|
resp = "USER " .. svr.auth_user .. " 0 * " .. svr.auth_user,
|
|
|
|
@ -551,6 +570,7 @@ itte.docs.traverse_channels = [[ (context_table, mode_str, channels_table)
|
|
|
|
|
]]
|
|
|
|
|
function itte.traverse_channels(cxt, mode, chans)
|
|
|
|
|
local channels = cxt.channels
|
|
|
|
|
cxt.chan_meta = {}
|
|
|
|
|
if chans ~= nil then channels = chans end
|
|
|
|
|
|
|
|
|
|
if mode == "join" then
|
|
|
|
@ -560,6 +580,7 @@ function itte.traverse_channels(cxt, mode, chans)
|
|
|
|
|
for c = 1, #channels do
|
|
|
|
|
if not util.has_key(cxt.channels, channels[c]) then
|
|
|
|
|
table.insert(cxt.channels, channels[c])
|
|
|
|
|
cxt.chan_meta[channels[c]:sub(2)] = {}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
@ -570,68 +591,136 @@ function itte.traverse_channels(cxt, mode, chans)
|
|
|
|
|
for c = 1, #channels do
|
|
|
|
|
if util.has_key(cxt.channels, channels[c]) then
|
|
|
|
|
table.remove(cxt.channels, util.find_key(cxt.channels, channels[c]))
|
|
|
|
|
cxt.chan_meta[channels[c]:sub(2)] = nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
itte.docs.parse_privmsg = [[ (context_table, data_str)
|
|
|
|
|
Given the context table and a PRIVMSG data string, parse the string and
|
|
|
|
|
return an associative table of values.
|
|
|
|
|
itte.docs.parse_privmsg = [[ (context_table, data_str [, mode_str])
|
|
|
|
|
Given the context table, a PRIVMSG data string and mode, parse the string and
|
|
|
|
|
return an associative table of values. If "code" mode is specified, it will
|
|
|
|
|
check the body for a service code. If "target" mode is specified, it will
|
|
|
|
|
extract the message body excluding the bot nick, but will not look for a
|
|
|
|
|
service code. The default mode is "code" if no mode is specified.
|
|
|
|
|
]]
|
|
|
|
|
function itte.parse_privmsg(cxt, str)
|
|
|
|
|
local code_full = ""
|
|
|
|
|
function itte.parse_privmsg(cxt, str, mode)
|
|
|
|
|
local msg = {}
|
|
|
|
|
-- Separator marks the start of the message body
|
|
|
|
|
body_sep, _ = string.find(str, ":", 2)
|
|
|
|
|
-- Service code found with specified prefix
|
|
|
|
|
if util.is_substr(str, ":" .. cxt.code_prefix) then
|
|
|
|
|
code_full = string.sub(str, string.find(str, ":" ..
|
|
|
|
|
cxt.code_prefix) + 1 + string.len(cxt.code_prefix))
|
|
|
|
|
end
|
|
|
|
|
local msg = {
|
|
|
|
|
sender = string.sub(str, string.find(str, "!") + 2,
|
|
|
|
|
string.find(str, "@") - 1),
|
|
|
|
|
recipient = string.sub(str, string.find(str, "PRIVMSG") + 8,
|
|
|
|
|
string.find(str, ":" .. cxt.code_prefix) - 2),
|
|
|
|
|
reply_to = string.sub(str, string.find(str, "!") + 2,
|
|
|
|
|
string.find(str, "@") - 1),
|
|
|
|
|
body = string.sub(str, body_sep + 1),
|
|
|
|
|
}
|
|
|
|
|
if util.is_substr(code_full, " ") then
|
|
|
|
|
msg.code = string.sub(code_full, 0, string.find(code_full, " ") - 1)
|
|
|
|
|
msg.code_params = util.split_str(string.sub(code_full,
|
|
|
|
|
string.find(code_full, " ") + 1))
|
|
|
|
|
|
|
|
|
|
-- Message in the format `[nick]: [body]`
|
|
|
|
|
if mode == "target" then
|
|
|
|
|
msg = {
|
|
|
|
|
sender = string.sub(str, string.find(str, "!") + 2,
|
|
|
|
|
string.find(str, "@") - 1),
|
|
|
|
|
-- Escape magic character "-" in the nick
|
|
|
|
|
recipient = string.sub(str, string.find(str, "PRIVMSG") + 8,
|
|
|
|
|
string.find(str, ":" .. string.gsub(cxt.nick, "-", "%%-")) - 2),
|
|
|
|
|
reply_to = string.sub(str, string.find(str, "!") + 2,
|
|
|
|
|
string.find(str, "@") - 1),
|
|
|
|
|
body = string.gsub(string.sub(str, body_sep + 2 +
|
|
|
|
|
string.len(cxt.nick)), "^%s", ""),
|
|
|
|
|
}
|
|
|
|
|
-- Reply to sender by default (private message), or reply in a channel if
|
|
|
|
|
-- original message recipient is a channel.
|
|
|
|
|
if util.is_substr(msg.recipient, "#") then
|
|
|
|
|
msg.reply_to = msg.recipient
|
|
|
|
|
end
|
|
|
|
|
util.debug(itte.config.debugs.privmsg[1], "{ " ..
|
|
|
|
|
itte.config.debugs.privmsg[2] .. ": " .. msg.sender ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[3] .. ": " .. msg.recipient ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[4] .. ": " .. msg.reply_to ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[7] .. ": " .. msg.body ..
|
|
|
|
|
" }", itte.config.debug)
|
|
|
|
|
|
|
|
|
|
-- Message in the format `[code] [params]`
|
|
|
|
|
else
|
|
|
|
|
msg.code = code_full
|
|
|
|
|
msg.code_params = {}
|
|
|
|
|
end
|
|
|
|
|
-- Reply to sender by default (private message), or reply in a channel if
|
|
|
|
|
-- original message recipient is a channel.
|
|
|
|
|
if util.is_substr(msg.recipient, "#") then
|
|
|
|
|
msg.reply_to = msg.recipient
|
|
|
|
|
end
|
|
|
|
|
local code_full = ""
|
|
|
|
|
-- Service code found with specified prefix
|
|
|
|
|
if util.is_substr(str, ":" .. cxt.code_prefix) then
|
|
|
|
|
code_full = string.sub(str, string.find(str, ":" ..
|
|
|
|
|
cxt.code_prefix) + 1 + string.len(cxt.code_prefix))
|
|
|
|
|
end
|
|
|
|
|
msg = {
|
|
|
|
|
sender = string.sub(str, string.find(str, "!") + 2,
|
|
|
|
|
string.find(str, "@") - 1),
|
|
|
|
|
recipient = string.sub(str, string.find(str, "PRIVMSG") + 8,
|
|
|
|
|
string.find(str, ":" .. cxt.code_prefix) - 2),
|
|
|
|
|
reply_to = string.sub(str, string.find(str, "!") + 2,
|
|
|
|
|
string.find(str, "@") - 1),
|
|
|
|
|
body = string.sub(str, body_sep + 1),
|
|
|
|
|
}
|
|
|
|
|
if util.is_substr(code_full, " ") then
|
|
|
|
|
msg.code = string.sub(code_full, 0, string.find(code_full, " ") - 1)
|
|
|
|
|
msg.code_params = util.split_str(string.sub(code_full,
|
|
|
|
|
string.find(code_full, " ") + 1))
|
|
|
|
|
|
|
|
|
|
-- Redact debug output for admin-only handlers to avoid logging passwords.
|
|
|
|
|
local body = msg.body
|
|
|
|
|
local code_params = table.concat(msg.code_params, " ")
|
|
|
|
|
if util.has_key(itte.config.admin_handlers, msg.code) then
|
|
|
|
|
body = itte.config.debugs.redact[1]
|
|
|
|
|
code_params = itte.config.debugs.redact[1]
|
|
|
|
|
else
|
|
|
|
|
msg.code = code_full
|
|
|
|
|
msg.code_params = {}
|
|
|
|
|
end
|
|
|
|
|
-- Reply to sender by default (private message), or reply in a channel if
|
|
|
|
|
-- original message recipient is a channel.
|
|
|
|
|
if util.is_substr(msg.recipient, "#") then
|
|
|
|
|
msg.reply_to = msg.recipient
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Redact debug output for admin-only handlers to avoid logging passwords.
|
|
|
|
|
local body = msg.body
|
|
|
|
|
local code_params = table.concat(msg.code_params, " ")
|
|
|
|
|
if util.has_key(itte.config.admin_handlers, msg.code) then
|
|
|
|
|
body = itte.config.debugs.redact[1]
|
|
|
|
|
code_params = itte.config.debugs.redact[1]
|
|
|
|
|
end
|
|
|
|
|
util.debug(itte.config.debugs.privmsg[1], "{ " ..
|
|
|
|
|
itte.config.debugs.privmsg[2] .. ": " .. msg.sender ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[3] .. ": " .. msg.recipient ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[4] .. ": " .. msg.reply_to ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[5] .. ": " .. msg.code ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[6] .. ": " .. code_params ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[7] .. ": " .. body ..
|
|
|
|
|
" }", itte.config.debug)
|
|
|
|
|
end
|
|
|
|
|
util.debug("privmsg", "{ " ..
|
|
|
|
|
itte.config.debugs.privmsg[1] .. ": " .. msg.sender ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[2] .. ": " .. msg.recipient ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[3] .. ": " .. msg.reply_to ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[4] .. ": " .. msg.code ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[5] .. ": " .. code_params ..
|
|
|
|
|
", " .. itte.config.debugs.privmsg[6] .. ": " .. body ..
|
|
|
|
|
" }", itte.config.debug)
|
|
|
|
|
return msg
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
itte.docs.parse_names = [[ (context_table, data_str)
|
|
|
|
|
Given the context table and a NAMES data string, parse the string and
|
|
|
|
|
return the channel name and a table of users.
|
|
|
|
|
]]
|
|
|
|
|
function itte.parse_names(cxt, str)
|
|
|
|
|
-- Separator marks the start of the message body
|
|
|
|
|
body_sep, _ = string.find(str, ":", 2)
|
|
|
|
|
local channel = string.sub(str, string.find(str, "#"),
|
|
|
|
|
string.find(str, ":", 2) - 2)
|
|
|
|
|
local names = util.split_str(string.sub(str, body_sep + 1))
|
|
|
|
|
util.debug(itte.config.debugs.names[1],
|
|
|
|
|
itte.config.debugs.names[2] .. ": " .. channel ..
|
|
|
|
|
", " .. itte.config.debugs.names[3] .. ": { " .. table.concat(names, ", ")
|
|
|
|
|
.. " }", itte.config.debug)
|
|
|
|
|
return channel, names
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
itte.docs.get_users = [[ (context_table, channel_str)
|
|
|
|
|
Given the context table and a channel, return a table of users in the
|
|
|
|
|
channel.
|
|
|
|
|
]]
|
|
|
|
|
function itte.get_users(cxt, str)
|
|
|
|
|
local chan = str
|
|
|
|
|
if string.find(chan, "#") == 1 then chan = str:sub(2) end
|
|
|
|
|
if not util.has_key(cxt.chan_meta, chan) then
|
|
|
|
|
do return end
|
|
|
|
|
end
|
|
|
|
|
-- Query server for the latest list of names
|
|
|
|
|
itte.send_command(cxt.con, cxt.cmds.names.resp .. " #" .. chan)
|
|
|
|
|
return cxt.chan_meta[chan].users
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
|
-- Service code handlers
|
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
@ -775,9 +864,10 @@ function itte._h.help(cxt, msg)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local custom_h = util.table_keys(itte.handlers)
|
|
|
|
|
-- Remove task handlers
|
|
|
|
|
-- Remove target and task handlers
|
|
|
|
|
for h = 1, #custom_h do
|
|
|
|
|
if string.find(custom_h[h], itte.config.task_handler_prefix) == 1 then
|
|
|
|
|
if (string.find(custom_h[h], itte.config.target_handler) == 1) or
|
|
|
|
|
(string.find(custom_h[h], itte.config.task_handler_prefix) == 1) then
|
|
|
|
|
custom_h[h] = nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
@ -840,6 +930,13 @@ function itte.listen(name, str)
|
|
|
|
|
itte.send_command(cxt.con, string.gsub(str, cxt.cmds.ping.check,
|
|
|
|
|
cxt.cmds.ping.resp))
|
|
|
|
|
|
|
|
|
|
-- Update channel names list
|
|
|
|
|
elseif string.find(str, ":" .. cxt.host .. " " .. cxt.cmds.names.check) == 1
|
|
|
|
|
then
|
|
|
|
|
util.debug(itte.config.debugs.listen[1], str, itte.config.debug)
|
|
|
|
|
local channel, names = itte.parse_names(cxt, str)
|
|
|
|
|
cxt.chan_meta[channel:sub(2)] = { users = names }
|
|
|
|
|
|
|
|
|
|
-- Respond to service code
|
|
|
|
|
elseif util.is_substr(str, cxt.cmds.privmsg.check) then
|
|
|
|
|
local msg = itte.parse_privmsg(cxt, str)
|
|
|
|
@ -852,8 +949,9 @@ function itte.listen(name, str)
|
|
|
|
|
util.debug(itte.config.debugs.listen[1], str, itte.config.debug)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Ignore calls to task handlers
|
|
|
|
|
if (string.find(msg.code, itte.config.task_handler_prefix) == 1) then
|
|
|
|
|
-- Ignore calls to the target and task handlers
|
|
|
|
|
if (string.find(msg.code, itte.config.target_handler) == 1) or
|
|
|
|
|
(string.find(msg.code, itte.config.task_handler_prefix) == 1) then
|
|
|
|
|
do return end
|
|
|
|
|
-- Check for the service code in the functions table before attempting to
|
|
|
|
|
-- call the function.
|
|
|
|
@ -870,6 +968,17 @@ function itte.listen(name, str)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Respond to direct address in a channel
|
|
|
|
|
elseif (util.is_substr(str, cxt.cmds.target_public.check)) or
|
|
|
|
|
(util.is_substr(str, cxt.cmds.target_private.check)) then
|
|
|
|
|
util.debug(itte.config.debugs.listen[1], str, itte.config.debug)
|
|
|
|
|
local msg = itte.parse_privmsg(cxt, str, "target")
|
|
|
|
|
|
|
|
|
|
-- Check for the target handler function before attempting to call it.
|
|
|
|
|
if util.has_key(itte.handlers, itte.config.target_handler) then
|
|
|
|
|
itte.handlers[itte.config.target_handler](cxt, msg)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Output to stdout anyway if debug is enabled
|
|
|
|
|
else
|
|
|
|
|
util.debug(itte.config.debugs.listen[1], str, itte.config.debug)
|
|
|
|
|