Plugins, REPL, auth, config.

master
q3k 2013-09-22 18:58:15 +02:00
parent ae280f16e0
commit 7062c3a7ff
7 changed files with 204 additions and 25 deletions

View File

@ -20,7 +20,6 @@ end
function bot:OnChannelMessage(Username, Channel, Message)
if Message:sub(1,#self._prefix) == self._prefix then
local String = Message:sub(#self._prefix + 1)
print(String)
local Command
local Arguments = {}
for Part in String:gmatch("%S+") do
@ -35,11 +34,33 @@ function bot:OnChannelMessage(Username, Channel, Message)
Channel:Say("Unknown command '" .. Command .. "'.")
else
local CommandData = self._commands[Command]
if #Arguments ~= CommandData.Arguments then
if #Arguments ~= CommandData.Arguments and CommandData.Arguments ~= -1 then
Channel:Say(string.format("Command '%s' expects '%i' arguments, got '%i'.",
Command, CommandData.Arguments, #Arguments))
else
CommandData.Callback(Username, Channel, unpack(Arguments))
-- -1 means we want a raw string
if CommandData.Arguments == -1 then
if #Arguments < 1 then
Channel:Say("Please provide an argument.")
return
end
Arguments = { table.concat(Arguments, ' ') }
end
local RequiredAccess = CommandData.Access
if RequiredAcess == 0 then
CommandData.Callback(Username, Channel, unpack(Arguments))
return
end
local UserAccess = hook.Call("auth.GetLevel", Channel, Username)
if not UserAccess then
Channel:Say("Could not run command because auth backend is missing.")
return
end
if UserAccess >= RequiredAccess then
CommandData.Callback(Username, Channel, unpack(Arguments))
else
Channel:Say(string.format("Unsufficient access level (%i required).", RequiredAccess))
end
end
end
end

40
core/config.lua Normal file
View File

@ -0,0 +1,40 @@
config = {}
function config:Load(filename)
local File, Message = io.open(filename, "r")
if not File then
error("Could not open config file: " .. Message)
end
self.Sections = {}
local CurrentSection = "Default"
self.Sections[CurrentSection] = {}
local Data = File:read('*a')
Data:gsub("(.-)\r?\n", function(line)
local Line = line:gsub("(#.*)", "")
Line = Line:gsub("^%s*(.-)%s*$", "%1")
if Line:len() == 0 then
return
end
local Section = Line:match("%[([a-zA-Z0-9%-]+)%]")
if Section ~= nil then
CurrentSection = Section
self.Sections[CurrentSection] = {}
else
local Key, Value = Line:match("([^= ]+) *= *([^=]+)")
print(Key,Value)
if Key ~= nil and Value ~= nil then
self.Sections[CurrentSection][Key] = Value
end
end
end)
end
function config:Get(Section, Key)
if self.Sections[Section] == nil then
return nil
end
return self.Sections[Section][Key]
end

View File

@ -4,6 +4,8 @@
-- The goal is to be able to unload a plugin and remove all its' hooks
-- and commands.
require('lfs')
local Plugins = {}
plugin = {}
@ -19,10 +21,10 @@ function API.AddHook(plugin_id, event_name, hook_name, callback)
HookInfo.EventName = event_name
HookInfo.HookName = HookName
PluginHooks[#PluginHooks + 1] = Info
PluginHooks[#PluginHooks + 1] = HookInfo
hook.Add(event_name, HookName, function(...)
local Args = {...}
local Success, Message = pcall(callback(unpack(Args)))
local Success, Message = pcall(callback, unpack(Args))
if not Success then
hook.Call("plugin.HookCallFailed", HookName, Message)
return nil
@ -33,7 +35,10 @@ function API.AddHook(plugin_id, event_name, hook_name, callback)
end
function API.AddCommand(plugin_id, command_name, arity, callback, access, help)
bot:AddCommand(command_name, arity, callback, access, help)
bot:AddCommand(command_name, arity, function(...)
plugin.Quota(5)
return callback(...)
end, access, help)
local PluginCommands = Plugins[plugin_id].Commands
PluginCommands[#PluginCommands + 1] = command_name
end
@ -51,6 +56,20 @@ function API.CurrentTime(plugin_id)
return os.time()
end
function API.ConfigGet(plugin_id, Key)
return config:Get("plugin-" .. plugin_id, Key)
end
function plugin.Quota(seconds)
local Start = os.time()
debug.sethook(function()
if os.time() - Start > seconds then
debug.sethook()
error("Time quota exceeded.")
end
end, "", 100000)
end
function plugin.Create(plugin_id)
local Plugin = {}
Plugin.Hooks = {}
@ -97,6 +116,22 @@ function plugin.PrepareEnvironment(plugin_id)
Env.table = require('table')
Env.string = require('string')
Env.json = require('json')
Env.DBI = require('DBI')
Env.print = print
Env.error = error
Env.tonumber = tonumber
Env.tostring = tostring
Env.pcall = pcall
Env.loadstring = function(s)
if s:byte(1) == 27 then
return nil, "Refusing to load bytecode"
else
return loadstring(s)
end
end
Env.setfenv = setfenv
Env.pairs = pairs
Env._G = Env
Env.plugin = {}
for K, F in pairs(API) do
@ -156,3 +191,25 @@ function plugin.AddRuntimeCommands()
end
end, "Unload a previously loaded plugin.", 100)
end
function plugin.Discover()
for Filename in lfs.dir('plugins/') do
local FullFilename = 'plugins/' .. Filename
local Attributes = lfs.attributes(FullFilename)
if Attributes.mode == 'file' and FullFilename:sub(-4) == '.lua' then
local PluginName = Filename:sub(1, -5)
hook.Call('info', 'Loading plugin ' .. PluginName)
local File, Message = io.open(FullFilename)
if not File then
hook.Call('info', 'Skipping: ' .. Message)
else
local Data = File:read('*a')
local Success, Message = plugin.RunCode(PluginName, Data)
if not Success then
error(string.format("Could not load plugin %s: %s.", PluginName, Message))
end
end
end
end
end

13
moonspeak.ini.dist Normal file
View File

@ -0,0 +1,13 @@
[irc]
server = irc.freenode.net
port = 6667
nickname = moonspeak
username = moonspeak
realname = moonspeak
[plugin-auth-postgres]
server = 1.1.1.1
username = user
database = db
password = pass

29
plugins/auth-postgres.lua Normal file
View File

@ -0,0 +1,29 @@
postgres = {}
local function check_connection()
if not postgres.db or not postgres.db:ping() then
local Server = plugin.ConfigGet('server')
local Username = plugin.ConfigGet('username')
local Password = plugin.ConfigGet('password')
local Database = plugin.ConfigGet('database')
local Port = tonumber(plugin.ConfigGet('port')) or 5432
postgres.db = DBI.Connect('PostgreSQL', Database, Username, Password, Server, Port)
end
if not postgres.db then
error("Could not connect to the PostgreSQL database!")
return false
end
return true
end
plugin.AddHook('auth.GetLevel', 'GetLevel', function(Channel, Account)
if check_connection() then
local Statement = postgres.db:prepare("select _level from _level where _account = ? and _channel = ?")
print(Account, Channel.Name)
Statement:execute(Account, Channel.Name)
for Row in Statement:rows(true) do
return tonumber(Row._level)
end
return 0
end
end)

24
plugins/repl.lua Normal file
View File

@ -0,0 +1,24 @@
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
Env.plugin = nil
Env.loadstring = nil
Env.pcall = nil
Env.setfenv = nil
Env._G = Env
setfenv(Function, Env)
local Result, Message = pcall(Function)
if Result then
Channel:Say("OK -> " .. tostring(Message))
else
Channel:Say("Error -> " .. tostring(Message))
end
end, "Runs a Lua command in a sandbox.", 10)

View File

@ -1,13 +1,10 @@
require('core.hook')
require('core.config')
require('core.reactor')
require('core.irc')
require('core.bot')
require('core.plugin')
require('socket')
local https = require('ssl.https')
require('json')
hook.Add('info', 'repl-info', function(Message)
print('INFO: ' .. Message)
end)
@ -16,26 +13,24 @@ hook.Add('debug', 'repl-debug', function(Message)
print('DEBUG: ' .. Message)
end)
hook.Add('plugin.HookCallFailed', 'repl-debug', function(Name, Message)
print(string.format("Plugin hook call failed! %s: %s", Name, Message))
end)
hook.Add('irc.Connected', 'repl-connected', function()
irc:Join('#hackerspace-pl-bottest')
end)
config:Load('moonspeak.ini')
local Server = config:Get('irc', 'server')
local Port = tonumber(config:Get('irc', 'port')) or 6667
local Nickname = config:Get('irc', 'nickname')
local Username = config:Get('irc', 'username')
local Realname = config:Get('irc', 'realname')
reactor:Initialize()
bot:Initialize(irc, ',')
plugin.AddRuntimeCommands()
--[[bot:AddCommand('at', 0, function(Username, Channel)
local Body, Code, Headers, Status = https.request('https://at.hackerspace.pl/api')
if Code ~= 200 then
error(string.format("Status code returned: %i", Code))
end
local Data = json.decode.decode(Body)
local Users = {}
for K, User in pairs(Data.users) do
Users[#Users + 1] = User.login
end
Channel:Say(table.concat(Users, ','))
end, "Show who's at the Warsaw Hackerspace.")]]--
irc:Connect('irc.freenode.net', 6667, 'moonspeak', 'moonspeak', 'moonspeak')
plugin.Discover()
irc:Connect(Server, Port, Nickname, Username, Realname)
reactor:Run()