diff --git a/core/plugin.lua b/core/plugin.lua new file mode 100644 index 0000000..bd651d0 --- /dev/null +++ b/core/plugin.lua @@ -0,0 +1,95 @@ +-- An isolated API for plugins +-- These plugins are not yet made to be a secure sandbox, as hooking +-- into signal might be destructive or reveal sensitive data. +-- The goal is to be able to unload a plugin and remove all its' hooks +-- and commands. + +local Plugins = {} + +plugin = {} +local API = {} + +-- All these functions start with the 'plugin_id' argument, which +-- will be auto-bound to the plugin name when called from a plugin. +function API.HookAdd(plugin_id, event_name, hook_name, callback) + local HookName = plugin_id .. ':' .. hook_name + local PluginHooks = Plugins[plugin_id].Hooks + PluginHooks[#PluginHooks + 1] = HookName + hook.Add(event_name, HookName, function(...) + local Args = {...} + local Success, Message = pcall(callback(unpack(Args))) + if not Success then + hook.Call("plugin.HookCallFailed", HookName, Message) + return nil + else + return Message + end + end) +end + +function API.CommandAdd(plugin_id, command_name, arity, callback, access, help) + bot:AddCommand(command_name, arity, callback, access, help) + local PluginCommands = Plugins[plugin_id].Commands + PluginCommands[#PluginCommands + 1] = command_name +end + +function API.Register(plugin_id, plugin_name, version, url, author) + local Plugin = Plugins[plugin_id] + Plugin.Name = plugin_name + Plugin.Version = Version + Plugin.URL = url + Plugin.Author = author + +end + +function plugin.Create(plugin_id) + local Plugin = {} + Plugin.Hooks = {} + Plugin.Commands = {} + + Plugin.ID = plugin_id + Plugin.Name = "" + Plugin.Version = 1.0 + Plugin.URL = "" + Plugin.Author = "Nameless Wonder" + + Plugins[plugin_id] = Plugin + Plugins[plugin_id].Env = plugin.PrepareEnvironment(plugin_id) +end + +function plugin.PrepareEnvironment(plugin_id) + local function BindPluginID(f) + return function(...) + local Args = {...} + return f(plugin_id, unpack(Args)) + end + end + + local Env = {} + Env.table = require('table') + Env.string = require('string') + Env.json = require('json') + + Env.plugin = {} + for K, F in pairs(API) do + Env.plugin[K] = BindPluginID(F) + end + + return Env +end + +function plugin.RunCode(plugin_id, code) + if not Plugins[plugin_id] then + plugin.Create(plugin_id) + end + + if code:byte(1) == 27 then + return nil, "Refused to load bytecode." + end + local Function, Message = loadstring(code) + if not Function then + return nil, Message + end + setfenv(Function, Plugins[plugin_id].Env) + return pcall(Function) +end diff --git a/start.lua b/start.lua index 9ccf719..efe0154 100644 --- a/start.lua +++ b/start.lua @@ -2,6 +2,7 @@ require('core.hook') require('core.reactor') require('core.irc') require('core.bot') +require('core.plugin') require('socket') local https = require('ssl.https') @@ -22,7 +23,7 @@ end) reactor:Initialize() bot:Initialize(irc, ',') -bot:AddCommand('at', 0, function(Username, Channel) +--[[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)) @@ -34,7 +35,11 @@ bot:AddCommand('at', 0, function(Username, Channel) end Channel:Say(table.concat(Users, ',')) -end, "Show who's at the Warsaw Hackerspace.") +end, "Show who's at the Warsaw Hackerspace.")]]-- + +plugin.RunCode('test', [[plugin.CommandAdd('test', 0, function(Username, Channel) + Channel:Say('hello!') +end, "Test command.", 0)]]) irc:Connect('irc.freenode.net', 6667, 'moonspeak', 'moonspeak', 'moonspeak') reactor:Run()