Plugins, REPL, auth, config.
parent
ae280f16e0
commit
7062c3a7ff
27
core/bot.lua
27
core/bot.lua
|
@ -20,7 +20,6 @@ end
|
||||||
function bot:OnChannelMessage(Username, Channel, Message)
|
function bot:OnChannelMessage(Username, Channel, Message)
|
||||||
if Message:sub(1,#self._prefix) == self._prefix then
|
if Message:sub(1,#self._prefix) == self._prefix then
|
||||||
local String = Message:sub(#self._prefix + 1)
|
local String = Message:sub(#self._prefix + 1)
|
||||||
print(String)
|
|
||||||
local Command
|
local Command
|
||||||
local Arguments = {}
|
local Arguments = {}
|
||||||
for Part in String:gmatch("%S+") do
|
for Part in String:gmatch("%S+") do
|
||||||
|
@ -35,11 +34,33 @@ function bot:OnChannelMessage(Username, Channel, Message)
|
||||||
Channel:Say("Unknown command '" .. Command .. "'.")
|
Channel:Say("Unknown command '" .. Command .. "'.")
|
||||||
else
|
else
|
||||||
local CommandData = self._commands[Command]
|
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'.",
|
Channel:Say(string.format("Command '%s' expects '%i' arguments, got '%i'.",
|
||||||
Command, CommandData.Arguments, #Arguments))
|
Command, CommandData.Arguments, #Arguments))
|
||||||
else
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -4,6 +4,8 @@
|
||||||
-- The goal is to be able to unload a plugin and remove all its' hooks
|
-- The goal is to be able to unload a plugin and remove all its' hooks
|
||||||
-- and commands.
|
-- and commands.
|
||||||
|
|
||||||
|
require('lfs')
|
||||||
|
|
||||||
local Plugins = {}
|
local Plugins = {}
|
||||||
|
|
||||||
plugin = {}
|
plugin = {}
|
||||||
|
@ -19,10 +21,10 @@ function API.AddHook(plugin_id, event_name, hook_name, callback)
|
||||||
HookInfo.EventName = event_name
|
HookInfo.EventName = event_name
|
||||||
HookInfo.HookName = HookName
|
HookInfo.HookName = HookName
|
||||||
|
|
||||||
PluginHooks[#PluginHooks + 1] = Info
|
PluginHooks[#PluginHooks + 1] = HookInfo
|
||||||
hook.Add(event_name, HookName, function(...)
|
hook.Add(event_name, HookName, function(...)
|
||||||
local Args = {...}
|
local Args = {...}
|
||||||
local Success, Message = pcall(callback(unpack(Args)))
|
local Success, Message = pcall(callback, unpack(Args))
|
||||||
if not Success then
|
if not Success then
|
||||||
hook.Call("plugin.HookCallFailed", HookName, Message)
|
hook.Call("plugin.HookCallFailed", HookName, Message)
|
||||||
return nil
|
return nil
|
||||||
|
@ -33,7 +35,10 @@ function API.AddHook(plugin_id, event_name, hook_name, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
function API.AddCommand(plugin_id, command_name, arity, callback, access, help)
|
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
|
local PluginCommands = Plugins[plugin_id].Commands
|
||||||
PluginCommands[#PluginCommands + 1] = command_name
|
PluginCommands[#PluginCommands + 1] = command_name
|
||||||
end
|
end
|
||||||
|
@ -51,6 +56,20 @@ function API.CurrentTime(plugin_id)
|
||||||
return os.time()
|
return os.time()
|
||||||
end
|
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)
|
function plugin.Create(plugin_id)
|
||||||
local Plugin = {}
|
local Plugin = {}
|
||||||
Plugin.Hooks = {}
|
Plugin.Hooks = {}
|
||||||
|
@ -97,6 +116,22 @@ function plugin.PrepareEnvironment(plugin_id)
|
||||||
Env.table = require('table')
|
Env.table = require('table')
|
||||||
Env.string = require('string')
|
Env.string = require('string')
|
||||||
Env.json = require('json')
|
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 = {}
|
Env.plugin = {}
|
||||||
for K, F in pairs(API) do
|
for K, F in pairs(API) do
|
||||||
|
@ -156,3 +191,25 @@ function plugin.AddRuntimeCommands()
|
||||||
end
|
end
|
||||||
end, "Unload a previously loaded plugin.", 100)
|
end, "Unload a previously loaded plugin.", 100)
|
||||||
end
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
33
start.lua
33
start.lua
|
@ -1,13 +1,10 @@
|
||||||
require('core.hook')
|
require('core.hook')
|
||||||
|
require('core.config')
|
||||||
require('core.reactor')
|
require('core.reactor')
|
||||||
require('core.irc')
|
require('core.irc')
|
||||||
require('core.bot')
|
require('core.bot')
|
||||||
require('core.plugin')
|
require('core.plugin')
|
||||||
|
|
||||||
require('socket')
|
|
||||||
local https = require('ssl.https')
|
|
||||||
require('json')
|
|
||||||
|
|
||||||
hook.Add('info', 'repl-info', function(Message)
|
hook.Add('info', 'repl-info', function(Message)
|
||||||
print('INFO: ' .. Message)
|
print('INFO: ' .. Message)
|
||||||
end)
|
end)
|
||||||
|
@ -16,26 +13,24 @@ hook.Add('debug', 'repl-debug', function(Message)
|
||||||
print('DEBUG: ' .. Message)
|
print('DEBUG: ' .. Message)
|
||||||
end)
|
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()
|
hook.Add('irc.Connected', 'repl-connected', function()
|
||||||
irc:Join('#hackerspace-pl-bottest')
|
irc:Join('#hackerspace-pl-bottest')
|
||||||
end)
|
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()
|
reactor:Initialize()
|
||||||
bot:Initialize(irc, ',')
|
bot:Initialize(irc, ',')
|
||||||
plugin.AddRuntimeCommands()
|
plugin.AddRuntimeCommands()
|
||||||
--[[bot:AddCommand('at', 0, function(Username, Channel)
|
plugin.Discover()
|
||||||
local Body, Code, Headers, Status = https.request('https://at.hackerspace.pl/api')
|
irc:Connect(Server, Port, Nickname, Username, Realname)
|
||||||
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')
|
|
||||||
reactor:Run()
|
reactor:Run()
|
||||||
|
|
Loading…
Reference in New Issue