Fixed sandbox, added freenode ID, added coroutines.

master
q3k 2013-09-22 21:28:48 +02:00
parent 7062c3a7ff
commit 9e12656a18
7 changed files with 215 additions and 30 deletions

View File

@ -15,6 +15,19 @@ function bot:Initialize(IRC, Prefix)
Channel:Say("Whoops! Error when executing OnChannelMessage: " .. Error)
end
end)
self:AddCommand('eval-core', -1, function(Username, Channel, String)
local Function, Message = loadstring(String)
if not Function then
Channel:Say("Parse error: " .. Message)
return
end
local Status, Message = pcall(Function)
if not Status then
Channel:Say("Error -> " .. Message)
else
Channel:Say("OK -> " .. tostring(Message))
end
end, "Runs a Lua command in the bot context.", 100)
end
function bot:OnChannelMessage(Username, Channel, Message)
@ -51,7 +64,12 @@ function bot:OnChannelMessage(Username, Channel, Message)
CommandData.Callback(Username, Channel, unpack(Arguments))
return
end
local UserAccess = hook.Call("auth.GetLevel", Channel, Username)
local Account = hook.Call("auth.GetAccount", irc, Username)
if not Account then
Channel:Say("Please identify with NickServ.")
return
end
local UserAccess = hook.Call("auth.GetLevel", Channel, Account)
if not UserAccess then
Channel:Say("Could not run command because auth backend is missing.")
return

View File

@ -7,14 +7,12 @@ function irc.Channel:Say(Message)
self._irc:Say(self.Name, Message)
end
function irc:ReceiveData(socket)
-- Two seconds look okay for receiving a rest of line
socket:settimeout(2)
local Data, Error = socket:receive('*l')
if Error then
error('Could not receive IRC line: ' .. Error)
end
hook.Call('debug', Data)
function irc.Channel:Whois(Nickname)
self._irc:Whois(Nickname)
end
function irc:ReceiveData(Data)
--hook.Call('debug', Data)
local Prefix, Command, Arguments
if Data:sub(1, 1) == ':' then
local Pattern = ':([^ ]+) +([^ ]+) *(.*)'
@ -88,6 +86,7 @@ function irc:HandleCommand(Prefix, Command, Arguments)
end
elseif Number and (Number >= 300) and (Number < 400) then
-- IRC server response. some parts of us might be looking for these
hook.Call('irc.GetResponse' .. tostring(Number), unpack(Arguments))
if self._response_hooks[Number] ~= nil then
for K, Callback in pairs(self._response_hooks[Number]) do
if Callback(unpack(Arguments)) ~= false then
@ -112,9 +111,22 @@ function irc:OnResponse(Response, Callback)
end
function irc:_Send(message)
local Delay = (self._last_sent + self._send_delay) - os.time()
if Delay > 0 then
local Function = debug.gethook()
if Function ~= nil then
Function()
end
reactor:Sleep(Delay)
end
self._last_sent = os.time()
self.Socket:send(message..'\r\n')
end
function irc:Whois(nickname)
self:_Send('WHOIS ' .. nickname)
end
function irc:SetNick(nickname)
self:_Send('NICK '..nickname)
hook.Call('info', 'Changing nickname to ' .. self._nickname)
@ -126,7 +138,9 @@ function irc:LoginUser(username, realname)
end
function irc:Say(target, message)
self:_Send('PRIVMSG ' .. target .. ' :' .. message)
for Line in message:gmatch("[^\r\n]+") do
self:_Send('PRIVMSG ' .. target .. ' :' .. Line)
end
end
function irc:Join(channel)
@ -157,7 +171,6 @@ function irc:Join(channel)
return false
end
for Member in Members:gmatch("%S+") do
print(Member)
local Flag, Name = Member:match('([~@%&]?)(.+)')
local Data = {}
Data.Name = Name
@ -179,6 +192,8 @@ function irc:Connect(server, port, nickname, username, realname)
self._channels = {}
self._response_hooks = {}
self._last_sent = os.time()
self._send_delay = 0.5
-- Connection procedure (callback hell!)
local FinishedInitialNotices = function()

View File

@ -6,10 +6,13 @@
require('lfs')
local Plugins = {}
plugin = {}
local API = {}
plugin.Plugins = {}
plugin.API = {}
local Plugins = plugin.Plugins
local API = plugin.API
-- All these functions start with the 'plugin_id' argument, which
-- will be auto-bound to the plugin name when called from a plugin.
@ -34,10 +37,26 @@ function API.AddHook(plugin_id, event_name, hook_name, callback)
end)
end
function API.Sleep(plugin_id, Delay)
reactor:Sleep(Delay)
end
function API.WaitForEvent(plugin_id, Event)
reactor:WaitForEvent(Event)
end
function API.Event(plugin_id, Event)
reactor:Event(Event)
end
function API.AddCommand(plugin_id, command_name, arity, callback, access, help)
bot:AddCommand(command_name, arity, function(...)
plugin.Quota(5)
return callback(...)
local Result = callback(...)
debug.sethook()
return Result
end, access, help)
local PluginCommands = Plugins[plugin_id].Commands
PluginCommands[#PluginCommands + 1] = command_name
@ -63,11 +82,15 @@ end
function plugin.Quota(seconds)
local Start = os.time()
debug.sethook(function()
if debug.gethook() == nil then
-- Race condition - somebody already removed us.
return
end
if os.time() - Start > seconds then
debug.sethook()
error("Time quota exceeded.")
end
end, "", 100000)
end, "", 1)
end
function plugin.Create(plugin_id)
@ -105,6 +128,29 @@ function plugin.Unload(plugin_id)
end
function plugin.PrepareEnvironment(plugin_id)
local function DeepCopy(t)
local Copied = {}
local Result = {}
local function Internal(Out, In)
for K, V in pairs(In) do
local Type = type(V)
if Type == "string" or Type == "function" or Type == "number" then
Out[K] = V
elseif Type == "table" then
if Copied[V] ~= nil then
Out[K] = Copied[V]
else
Copied[V] = {}
Internal(Copied[V], V)
Out[K] = Copied[V]
end
end
end
return Out
end
Internal(Result, t)
return Result
end
local function BindPluginID(f)
return function(...)
local Args = {...}
@ -113,15 +159,16 @@ function plugin.PrepareEnvironment(plugin_id)
end
local Env = {}
Env.table = require('table')
Env.string = require('string')
Env.json = require('json')
Env.DBI = require('DBI')
Env.table = DeepCopy(require('table'))
Env.string = DeepCopy(require('string'))
Env.json = DeepCopy(require('json'))
Env.DBI = DeepCopy(require('DBI'))
Env.print = print
Env.error = error
Env.tonumber = tonumber
Env.tostring = tostring
Env.pcall = pcall
Env.type = type
Env.loadstring = function(s)
if s:byte(1) == 27 then
return nil, "Refusing to load bytecode"
@ -160,7 +207,7 @@ end
function plugin.AddRuntimeCommands()
bot:AddCommand('plugin-load', 1, function(Username, Channel, Name)
if not Name:match('([a-zA-Z0-9\-_]+)') then
if not Name:match('([a-zA-Z0-9%-_]+)') then
Channel:Say("Invalid plugin name!")
return
end

View File

@ -1,4 +1,5 @@
local socket = require('socket')
local coroutine = require('coroutine')
reactor = {}
@ -8,6 +9,10 @@ function reactor:Initialize(quantum)
self._quantum = quantum or 0.1
self._quit = false
self._co_sleep = {}
self._co_event = {}
self._coroutines = {}
self._timers = {}
end
@ -15,6 +20,41 @@ function reactor:Quit()
self._quit = true
end
function reactor:Sleep(time)
local co = coroutine.running()
if co == nil then
socket.sleep(time)
return
end
self._co_sleep[co] = os.time() + time
coroutine.yield()
end
function reactor:WaitForEvent(event)
local co = coroutine.running()
if co == nil then
error("Main thread waiting for event... wtf?")
end
self._co_event[co] = event
coroutine.yield()
end
function reactor:Event(event)
for Coroutine, Event in pairs(self._co_event) do
if Event == event then
coroutine.resume(Coroutine)
end
end
end
function reactor:Spawn(f, ...)
local Args = {...}
local co = coroutine.create(function() f(unpack(Args)) end)
self._coroutines[#self._coroutines + 1] = co
coroutine.resume(co)
end
function reactor:Run()
local read = {}
for Socket, V in pairs(self._read_sockets) do
@ -35,25 +75,35 @@ function reactor:Run()
-- we actually got something on our sockets
for Socket, Data in pairs(self._read_sockets) do
if r[Socket] ~= nil then
Socket:settimeout(2)
local Line, Error = Socket:receive('*l')
if Error then
error('Could not receive line: ' .. Error)
end
local Callback = Data[1]
local Args = Data[2]
Callback(Socket, unpack(Args))
hook.Call('SocketDataReceived', Socket)
self:Spawn(Callback, Line, unpack(Args))
end
end
for Socket, Data in pairs(self._write_sockets) do
if w[Socket] ~= nil then
local Callback = Data[1]
local Args = Data[2]
Callback(Socket, unpack(Args))
self:Spawn(Callback, Socket, unpack(Args))
end
end
end
-- See, if we should wake up any sleepers
for Coroutine, Timeout in pairs(self._co_sleep) do
if os.time() > Timeout then
self._co_sleep[Coroutine] = nil
coroutine.resume(Coroutine)
end
end
hook.Call('ReactorTick')
local Time = os.time()
for TimerName, Data in pairs(self._timers) do
if Time >= Data.NextTick then
print("Firing timer " .. TimerName)
local Result = Data.Callback()
if Data.Period ~= nil and Result ~= false then
Data.NextTick = Data.NextTick + Data.Period

View File

@ -0,0 +1,27 @@
local Map = {}
local Pending = {}
plugin.AddHook('auth.GetAccount', 'GetAccount', function(IRC, Username)
if Map[Username] == nil then
if not Pending[Username] then
IRC:Whois(Username)
Pending[Username] = true
end
plugin.WaitForEvent("whois-"..Username)
end
return Map[Username]
end)
plugin.AddHook('irc.GetResponse330', 'WHOISResponse', function(_, Username, Account, Message)
Map[Username] = Account
plugin.Event("whois-" .. Username)
end)
plugin.AddHook('irc.ChannelNames', 'ScanChannelUsers', function(Channel)
for Nick, Member in pairs(Channel.Members) do
if not Pending[Username] then
IRC:Whois(Username)
Pending[Username] = true
end
end
end)

View File

@ -1,18 +1,46 @@
local function DeepCopy(t)
local Copied = {}
local Result = {}
local function Internal(Out, In)
for K, V in pairs(In) do
local Type = type(V)
if Type == "string" or Type == "function" or Type == "number" then
Out[K] = V
elseif Type == "table" then
if Copied[V] ~= nil then
Out[K] = Copied[V]
else
Copied[V] = {}
Internal(Copied[V], V)
Out[K] = Copied[V]
end
end
end
return Out
end
Internal(Result, t)
return Result
end
plugin.AddCommand('eval', -1, function(User, Channel, String)
local Function, Message = loadstring(String)
if not Function then
Channel:Say("Parse error: " .. Message)
return
end
local Env = {}
for K, V in pairs(_G) do
Env[K] = V
end
local Env = DeepCopy(_G)
Env.plugin = nil
Env.loadstring = nil
Env.pcall = nil
Env.setfenv = nil
Env._G = Env
Env.DBI = nil
Env.print = function(...)
local Args = {...}
local Output = table.concat(Args, "\t")
Channel:Say("stdout: " .. Output)
end
setfenv(Function, Env)
local Result, Message = pcall(Function)

View File

@ -29,7 +29,7 @@ local Username = config:Get('irc', 'username')
local Realname = config:Get('irc', 'realname')
reactor:Initialize()
bot:Initialize(irc, ',')
bot:Initialize(irc, '~')
plugin.AddRuntimeCommands()
plugin.Discover()
irc:Connect(Server, Port, Nickname, Username, Realname)