mirror of https://github.com/nealey/irc-bot
Add channel moderation example
This commit is contained in:
parent
783d1b1b6a
commit
dc031c04c9
3
README
3
README
|
@ -37,6 +37,9 @@ advantage to this method is that your network client doesn't have to
|
|||
care about the transport mechanism: TCP, UDP, TCP6, SSL, or whatever
|
||||
else.
|
||||
|
||||
Other programs which work this way include `socat` and BSD `netcat`.
|
||||
Use whatever you prefer.
|
||||
|
||||
|
||||
bot
|
||||
---
|
||||
|
|
|
@ -0,0 +1,318 @@
|
|||
#! /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()
|
||||
|
Loading…
Reference in New Issue