mirror of https://github.com/nealey/irc-bot
319 lines
5.7 KiB
Lua
319 lines
5.7 KiB
Lua
|
#! /usr/bin/lua
|
||
|
|
||
|
channel = "testchan"
|
||
|
|
||
|
--
|
||
|
-- A bot framework with a bunch of nifty features you can
|
||
|
-- call from your privmsg handler.
|
||
|
--
|
||
|
|
||
|
-- You should override these in your handler. Don't change
|
||
|
-- them here, though, or you'll have to keep changing them
|
||
|
-- every time you install a new release.
|
||
|
bot = {}
|
||
|
bot.debug = false
|
||
|
|
||
|
bot.nick = channel .. "-moderator"
|
||
|
bot.user = "moderator"
|
||
|
bot.desc = "Channel Moderation Fairy"
|
||
|
bot.channels = {"#" .. channel, "#mod-" .. channel}
|
||
|
bot.triggers = {}
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Some convenience functions
|
||
|
--
|
||
|
|
||
|
function shesc(s)
|
||
|
return "'" .. s:gsub("'", "'\\''") .. "'"
|
||
|
end
|
||
|
|
||
|
function syscmd(argv)
|
||
|
local args = {}
|
||
|
local cmdline
|
||
|
local proc
|
||
|
|
||
|
for _,a in ipairs(argv) do
|
||
|
table.insert(args, shesc(a))
|
||
|
end
|
||
|
cmdline = table.concat(args, " ")
|
||
|
|
||
|
proc = io.popen(cmdline, "r")
|
||
|
return function ()
|
||
|
return proc:read()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function system(argv)
|
||
|
local ret = {}
|
||
|
|
||
|
for line in syscmd(argv) do
|
||
|
table.insert(ret, line)
|
||
|
end
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
|
||
|
function string:startswith(needle)
|
||
|
local len = needle:len()
|
||
|
|
||
|
return self:sub(1, len) == needle
|
||
|
end
|
||
|
|
||
|
function string:endswith(needle)
|
||
|
local len = needle:len()
|
||
|
|
||
|
return self:sub(-len) == needle
|
||
|
end
|
||
|
|
||
|
|
||
|
------------------------------------------------------------
|
||
|
-- Bot object
|
||
|
--
|
||
|
|
||
|
|
||
|
bot.prefix = os.getenv("prefix")
|
||
|
bot.forum = os.getenv("forum")
|
||
|
bot.sender = os.getenv("sender")
|
||
|
bot.command = os.getenv("command")
|
||
|
bot.text = os.getenv("text")
|
||
|
bot.args = arg
|
||
|
|
||
|
--
|
||
|
-- Log to stderr
|
||
|
--
|
||
|
function bot:log(text)
|
||
|
io.stderr:write(tostring(text) .. "\n")
|
||
|
end
|
||
|
|
||
|
--
|
||
|
-- Log what we are working with
|
||
|
--
|
||
|
function bot:debug_input(text)
|
||
|
self:log(("< %-8s %8s/%-8s :%s"):format(
|
||
|
self.command,
|
||
|
self.sender or "-",
|
||
|
self.forum or "-",
|
||
|
self.text or ""))
|
||
|
end
|
||
|
|
||
|
--
|
||
|
-- Send a raw IRC command
|
||
|
--
|
||
|
function bot:raw(text)
|
||
|
if self.debug then
|
||
|
self:log(" > " .. text)
|
||
|
end
|
||
|
print(text)
|
||
|
end
|
||
|
|
||
|
--
|
||
|
-- Send a message to the forum, falling back to sender if we're
|
||
|
-- spamming the channel.
|
||
|
--
|
||
|
function bot:msg(text)
|
||
|
self.msgs_sent = self.msgs_sent + 1
|
||
|
if ((self.msgs_sent == 5) and (self.forum ~= self.sender)) then
|
||
|
self:raw("PRIVMSG " .. self.forum .. " :Sending the rest in private")
|
||
|
self.msg_recip = self.sender
|
||
|
end
|
||
|
self:raw("PRIVMSG " .. self.msg_recip .. " :" .. text)
|
||
|
end
|
||
|
bot.msgs_sent = 0
|
||
|
bot.msg_recip = bot.forum
|
||
|
|
||
|
--
|
||
|
-- Emote, like "* botname explodes"
|
||
|
--
|
||
|
function bot:emote(text)
|
||
|
self:msg("\001ACTION " .. text .. "\001")
|
||
|
end
|
||
|
|
||
|
--
|
||
|
-- Run something in the shell
|
||
|
--
|
||
|
function bot:syscmd(argv)
|
||
|
for line in syscmd(argv) do
|
||
|
if line:sub(1, 1) == ':' then
|
||
|
self:emote(line:sub(2))
|
||
|
else
|
||
|
self:msg(line)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Process self.triggers
|
||
|
function bot:do_triggers()
|
||
|
for pat,func in pairs(self.triggers) do
|
||
|
local a1, a2, a3, a4, a5 = self.text:match(pat)
|
||
|
|
||
|
if a1 and func then
|
||
|
if type(func) == "string" then
|
||
|
self:msg(func)
|
||
|
else
|
||
|
func(self, a1, a2, a3, a4, a5, a6)
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Use introspection to dispatch a command
|
||
|
function bot:run()
|
||
|
local func = self["handle_" .. self.command:lower()]
|
||
|
|
||
|
if (self.debug) then
|
||
|
self:debug_input()
|
||
|
end
|
||
|
|
||
|
if (func) then
|
||
|
func(self)
|
||
|
else
|
||
|
self:handle_default()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Log in to IRC
|
||
|
function bot:handle__init_()
|
||
|
self:raw("NICK " .. self.nick)
|
||
|
self:raw(("USER %s %s %s :%s"):format(self.user, self.user, self.user, self.desc))
|
||
|
end
|
||
|
|
||
|
-- Join channels
|
||
|
function bot:handle_001()
|
||
|
self:raw("PRIVMSG nickserv :identify modbot4pescado.fleas")
|
||
|
end
|
||
|
|
||
|
-- Deal with nickname collision
|
||
|
function bot:handle_433()
|
||
|
self:raw("NICK " .. self.nick .. (os.time() % 500))
|
||
|
end
|
||
|
|
||
|
-- Override this to handle messages
|
||
|
function bot:handle_privmsg()
|
||
|
self:do_triggers()
|
||
|
end
|
||
|
|
||
|
-- Override this to handle any undefined command
|
||
|
function bot:handle_default()
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
---------------------------------
|
||
|
--
|
||
|
-- Moderation
|
||
|
--
|
||
|
--
|
||
|
require('lfs')
|
||
|
|
||
|
function queue()
|
||
|
ret = {}
|
||
|
for file in lfs.dir("queue") do
|
||
|
if file:sub(1, 1) ~= "." then
|
||
|
table.insert(ret, file)
|
||
|
end
|
||
|
end
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
function push(text)
|
||
|
now = os.time()
|
||
|
f = io.open(("queue/%d"):format(now), "w")
|
||
|
f:write(text)
|
||
|
f:close()
|
||
|
end
|
||
|
|
||
|
function pop(q, peek)
|
||
|
if not q then
|
||
|
q = queue()
|
||
|
end
|
||
|
if #q == 0 then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
table.sort(q)
|
||
|
fn = ("queue/%s"):format(q[1])
|
||
|
|
||
|
f = io.open(fn)
|
||
|
ret = f:read()
|
||
|
f:close()
|
||
|
|
||
|
if peek == nil then
|
||
|
os.remove(fn)
|
||
|
table.remove(q, 1)
|
||
|
end
|
||
|
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
function peek(q)
|
||
|
return pop(q, true)
|
||
|
end
|
||
|
|
||
|
function bot:handle_notice()
|
||
|
if (self.sender == 'NickServ') and self.text:startswith("You are successfully identified") then
|
||
|
for _,chan in ipairs(self.channels) do
|
||
|
self:raw("JOIN " .. chan)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function bot:handle_privmsg()
|
||
|
if self.forum:sub(1, 1) == "#" then
|
||
|
self:handle_public()
|
||
|
else
|
||
|
self:handle_private()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function bot:handle_public()
|
||
|
if self.forum:startswith("#mod-") then
|
||
|
q = queue()
|
||
|
if self.text:startswith("!") then
|
||
|
if self.text:startswith("!n") then
|
||
|
question = pop(q)
|
||
|
if not question then
|
||
|
return self:msg("[Queue empty]")
|
||
|
else
|
||
|
self:raw("PRIVMSG " .. self.channels[1] .. " :" .. question)
|
||
|
end
|
||
|
elseif self.text:startswith("!d") then
|
||
|
pop(q)
|
||
|
end
|
||
|
|
||
|
question = pop(q, true)
|
||
|
if question then
|
||
|
self:msg(("[1/%d] %s"):format(#q, question))
|
||
|
else
|
||
|
self:msg("[End of Queue]")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function bot:handle_private()
|
||
|
ok = false
|
||
|
|
||
|
for addr in io.lines("authorized.txt") do
|
||
|
if self.text:startswith(addr) then
|
||
|
ok = true
|
||
|
end
|
||
|
end
|
||
|
if not ok then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
q = queue()
|
||
|
question = ("<%s> %s"):format(self.prefix, self.text)
|
||
|
push(question)
|
||
|
self:msg("Your question has been enqueued. There are approximately " .. #q .. " questions before yours.")
|
||
|
|
||
|
if (#q == 0) then
|
||
|
self:raw("PRIVMSG " .. self.channels[2] .. " :[1/1] " .. question)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
bot.debug = true
|
||
|
bot:run()
|
||
|
|