From 38c9e580b9420a5c070fb9f4ad6e97ba8b21cdbe Mon Sep 17 00:00:00 2001 From: Yessiest Date: Fri, 20 May 2022 21:52:22 +0400 Subject: [PATCH] bustin --- libraries/classes/acl.lua | 116 +++++----- libraries/classes/baseclass.lua | 50 ++--- libraries/classes/command-acl.lua | 120 +++++------ libraries/classes/command-handler.lua | 164 +++++++------- libraries/classes/command.lua | 260 +++++++++++------------ libraries/classes/emitter-proxy.lua | 122 +++++------ libraries/classes/interfaces/discord.lua | 12 -- libraries/classes/monitor.lua | 90 -------- libraries/classes/plugin-handler.lua | 194 ++++++++--------- libraries/classes/plugin.lua | 70 +++--- libraries/classes/server-handler.lua | 160 +++++++------- plugins/debug/init.lua | 25 +++ 12 files changed, 645 insertions(+), 738 deletions(-) delete mode 100644 libraries/classes/interfaces/discord.lua delete mode 100644 libraries/classes/monitor.lua diff --git a/libraries/classes/acl.lua b/libraries/classes/acl.lua index 4029287..60e5414 100644 --- a/libraries/classes/acl.lua +++ b/libraries/classes/acl.lua @@ -6,87 +6,87 @@ local class = import("classes.baseclass") local table_utils = import("table-utils") local acl = class("ACL") function acl:__init() - self.user_rules = {} - self.group_rules = {} + self.user_rules = {} + self.group_rules = {} end function acl:set_user_rule(user_id,status) - assert( - (status == nil) or (status == 0) or (status == -1) or (status == 1), - "invalid status setting" - ) - self.user_rules[user_id] = status + assert( + (status == nil) or (status == 0) or (status == -1) or (status == 1), + "invalid status setting" + ) + self.user_rules[user_id] = status end function acl:set_group_rule(group_id,status) - assert( - (status == nil) or (status == 0) or (status == -1) or (status == 1), - "invalid status setting" - ) - self.group_rules[group_id] = status + assert( + (status == nil) or (status == 0) or (status == -1) or (status == 1), + "invalid status setting" + ) + self.group_rules[group_id] = status end function acl:check_user(user_id) - if self.user_rules[user_id] and self.user_rules[user_id] ~= 0 then - return true,(self.user_rules[user_id] == 1) - else - return false - end + if self.user_rules[user_id] and self.user_rules[user_id] ~= 0 then + return true,(self.user_rules[user_id] == 1) + else + return false + end end function acl:check_group(groups) - local allow = false - local found = false - for k,v in pairs(groups) do - if self.group_rules[v] then - found = true - allow = self.group_rules[v] + local allow = false + local found = false + for k,v in pairs(groups) do + if self.group_rules[v] then + found = true + allow = self.group_rules[v] + end end - end - return found,(allow and allow == 1) + return found,(allow and allow == 1) end function acl:export_all_lists() - local lists = { - users = "", - groups = "" - } - for k,v in pairs(self.user_rules) do - lists.users = lists.users..k..":"..tostring(v)..";\n" - end - for k,v in pairs(self.group_rules) do - lists.groups = lists.groups..k..":"..tostring(v)..";\n" - end - return lists + local lists = { + users = "", + groups = "" + } + for k,v in pairs(self.user_rules) do + lists.users = lists.users..k..":"..tostring(v)..";\n" + end + for k,v in pairs(self.group_rules) do + lists.groups = lists.groups..k..":"..tostring(v)..";\n" + end + return lists end function acl:export_user_list() - local list = "" - for k,v in pairs(self.user_rules) do - list = list..k..":"..tostring(v)..";\n" - end - return list + local list = "" + for k,v in pairs(self.user_rules) do + list = list..k..":"..tostring(v)..";\n" + end + return list end function acl:export_group_list() - local list = "" - for k,v in pairs(self.group_rules) do - list = list..k..":"..tostring(v)..";\n" - end - return list + local list = "" + for k,v in pairs(self.group_rules) do + list = list..k..":"..tostring(v)..";\n" + end + return list end function acl:export_snapshot() - return { - user_rules = bot_utils.deepcopy(self.user_rules), - group_rules = bot_utils.deepcopy(self.group_rules) - } + return { + user_rules = bot_utils.deepcopy(self.user_rules), + group_rules = bot_utils.deepcopy(self.group_rules) + } end function acl:import_snapshot(t) - self.user_rules = t.user_rules - self.group_rules = t.group_rules + self.user_rules = t.user_rules + self.group_rules = t.group_rules end function acl:import_user_list(list) - list:gsub("(%w+):(%d+)",function(id,status) - self.user_rules[id] = status - end) + list:gsub("(%w+):(%d+)",function(id,status) + self.user_rules[id] = status + end) end function acl:import_group_list(list) - list:gsub("(%w+):(%d+)",function(id,status) - self.group_rules[id] = status - end) + list:gsub("(%w+):(%d+)",function(id,status) + self.group_rules[id] = status + end) end return acl diff --git a/libraries/classes/baseclass.lua b/libraries/classes/baseclass.lua index fbef02c..49860d7 100644 --- a/libraries/classes/baseclass.lua +++ b/libraries/classes/baseclass.lua @@ -1,32 +1,32 @@ --class generator (for the purpose of creating classes) return function(name) - local new_class = {} - new_class.__classname = name or "Object" - new_class.__index = new_class - new_class.__new = function(self,...) - local obj = {} - --set metamethod proetection measures - setmetatable(obj,{__index = function(obj,key) - if key:find("^__") then - return nil - else - return self[key] - end - end, - __name = new_class.__classname}) - if self.__init then - self.__init(obj,...) - end - return obj - end - new_class.extend = function(self,name) local new_class = {} new_class.__classname = name or "Object" new_class.__index = new_class - setmetatable(new_class,{__index = self,__call = function(...) return new_class.__new(...) end, __name = new_class.__classname.." (class)"}) + new_class.__new = function(self,...) + local obj = {} + --set metamethod proetection measures + setmetatable(obj,{__index = function(obj,key) + if key:find("^__") then + return nil + else + return self[key] + end + end, + __name = new_class.__classname}) + if self.__init then + self.__init(obj,...) + end + return obj + end + new_class.extend = function(self,name) + local new_class = {} + new_class.__classname = name or "Object" + new_class.__index = new_class + setmetatable(new_class,{__index = self,__call = function(...) return new_class.__new(...) end, __name = new_class.__classname.." (class)"}) + return new_class + end + --make our class callable; on call, it will initialize a new instance of itself + setmetatable(new_class,{__call = function(...) return new_class.__new(...) end, __name = new_class.__classname.." (class)"}) return new_class - end - --make our class callable; on call, it will initialize a new instance of itself - setmetatable(new_class,{__call = function(...) return new_class.__new(...) end, __name = new_class.__classname.." (class)"}) - return new_class end diff --git a/libraries/classes/command-acl.lua b/libraries/classes/command-acl.lua index c7e5be2..a21de31 100644 --- a/libraries/classes/command-acl.lua +++ b/libraries/classes/command-acl.lua @@ -7,84 +7,84 @@ local enum_perms = discordia.enums.permission --The following method extends the ACL class to work with rule-specific features, --such as the role position function command_acl:check_group(roles) - local found = false - local highest_role = nil - local highest_role_status = nil - for k,v in pairs(roles) do - if self.group_rules[v.id] then - found = true - if not highest_role then - highest_role = v - highest_role_status = self.group_rules[v.id] - end - if v.position > highest_role.position then - highest_role = v - highest_role_status = self.group_rules[v.id] - end + local found = false + local highest_role = nil + local highest_role_status = nil + for k,v in pairs(roles) do + if self.group_rules[tostring(v.id)] then + found = true + if not highest_role then + highest_role = v + highest_role_status = self.group_rules[tostring(v.id)] + end + if v.position > highest_role.position then + highest_role = v + highest_role_status = self.group_rules[tostring(v.id)] + end + end end - end - local allow = highest_role_status - return found,(allow and allow == 1) + local allow = highest_role_status + return found,(allow and allow == 1) end --The following methods extend the ACL class to add the "perm" permissions --(the fallback when no rule/user permissions are found) function command_acl:__init() - self.user_rules = {} - self.group_rules = {} - self.perm_rules = {} + self.user_rules = {} + self.group_rules = {} + self.perm_rules = {} end function command_acl:check_perm(perms) - local output = true - for k,v in pairs(self.perm_rules) do - if (bit.band(perms[1],enum_perms[v]) == 0) then - output = false - end - end - return output + local output = true + for k,v in pairs(self.perm_rules) do + if (bit.band(perms[1],enum_perms[v]) == 0) then + output = false + end + end + return output end function command_acl:set_perm_rules(list) - assert(type(list)=="table","table expected, got "..type(list)) - self.perm_rules = list + assert(type(list)=="table","table expected, got "..type(list)) + self.perm_rules = list end function command_acl:export_all_lists() - local lists = { - users = "", - groups = "", - perm = "" - } - for k,v in pairs(self.user_rules) do - lists.users = lists.users..k..":"..tostring(v)..";\n" - end - for k,v in pairs(self.group_rules) do - lists.groups = lists.groups..k..":"..tostring(v)..";\n" - end - for k,v in pairs(self.perm_rules) do - lists.perm = lists.perm..k..":"..tostring(v)..";\n" - end - return lists + local lists = { + users = "", + groups = "", + perm = "" + } + for k,v in pairs(self.user_rules) do + lists.users = lists.users..k..":"..tostring(v)..";\n" + end + for k,v in pairs(self.group_rules) do + lists.groups = lists.groups..k..":"..tostring(v)..";\n" + end + for k,v in pairs(self.perm_rules) do + lists.perm = lists.perm..k..":"..tostring(v)..";\n" + end + return lists end function command_acl:export_perm_list() - local list = "" - for k,v in pairs(self.perm_rules) do - list = list..k..":"..tostring(v)..";\n" - end - return list + local list = "" + for k,v in pairs(self.perm_rules) do + list = list..k..":"..tostring(v)..";\n" + end + return list end function command_acl:export_snapshot() - return { - user_rules = table_utils.deepcopy(self.user_rules), - group_rules = table_utils.deepcopy(self.group_rules), - perm_rules = table_utils.deepcopy(self.perm_rules) - } + return { + user_rules = table_utils.deepcopy(self.user_rules), + group_rules = table_utils.deepcopy(self.group_rules), + perm_rules = table_utils.deepcopy(self.perm_rules) + } end function command_acl:import_snapshot(t) - self.user_rules = t.user_rules - self.group_rules = t.group_rules - self.perm_rules = t.perm_rules + self.user_rules = t.user_rules + self.group_rules = t.group_rules + self.perm_rules = t.perm_rules end function command_acl:import_perm_list() - list:gsub("(%w+):(%d+)",function(id,status) - self.perm_rules[id] = status - end) + list:gsub("(%w+):(%d+)",function(id,status) + self.perm_rules[id] = status + end) end return command_acl diff --git a/libraries/classes/command-handler.lua b/libraries/classes/command-handler.lua index 0bc2d9a..1f1575b 100644 --- a/libraries/classes/command-handler.lua +++ b/libraries/classes/command-handler.lua @@ -9,108 +9,108 @@ local command_handler = class("Command-handler") local table_utils = import("table-utils") local purify = import("purify") function command_handler:__init(parent_server) - self.server_handler = assert(parent_server,"parent server handler not provided") - self.command_pool = {} - self.prefixes = {} - self.command_meta = { - plugins = {}, - categories = {} - } + self.server_handler = assert(parent_server,"parent server handler not provided") + self.command_pool = {} + self.prefixes = {} + self.command_meta = { + plugins = {}, + categories = {} + } end function command_handler:add_prefix(prefix) - local purified_prefix = purify.purify_escapes(prefix) - self.prefixes[purified_prefix] = purified_prefix - return true + local purified_prefix = purify.purify_escapes(prefix) + self.prefixes[purified_prefix] = purified_prefix + return true end function command_handler:remove_prefix(prefix) - local purified_prefix = purify.purify_escapes(prefix) - if self.prefixes[purified_prefix] or table_utils.count(self.prefixes) <= 1 then - self.prefix[purified_prefix] = nil - return true - else - return false, ( - (self.prefixes[purified_prefix] and "No such prefix") or - "Cannot remove the last remaining prefix" - ) - end + local purified_prefix = purify.purify_escapes(prefix) + if self.prefixes[purified_prefix] or table_utils.count(self.prefixes) <= 1 then + self.prefix[purified_prefix] = nil + return true + else + return false, ( + (self.prefixes[purified_prefix] and "No such prefix") or + "Cannot remove the last remaining prefix" + ) + end end function command_handler:get_prefixes() - return table_utils.deepcopy(self.prefixes) + return table_utils.deepcopy(self.prefixes) end function command_handler:add_command(command) - assert(type(command) == "table","command object expected") - local purified_name = purify.purify_escapes(command.name) - self.command_pool[purified_name] = command - if not self.command_meta.plugins[command.parent.name] then - self.command_meta.plugins[command.parent.name] = {} - end - if not self.command_meta.categories[command.options.category] then - self.command_meta.categories[command.options.category] = {} - end - table.insert(self.command_meta.categories[command.options.category],command.name) - table.insert(self.command_meta.plugins[command.parent.name],command.name) - return command + assert(type(command) == "table","command object expected") + local purified_name = purify.purify_escapes(command.name) + self.command_pool[purified_name] = command + if not self.command_meta.plugins[command.parent.name] then + self.command_meta.plugins[command.parent.name] = {} + end + if not self.command_meta.categories[command.options.category] then + self.command_meta.categories[command.options.category] = {} + end + table.insert(self.command_meta.categories[command.options.category],command.name) + table.insert(self.command_meta.plugins[command.parent.name],command.name) + return command end function command_handler:remove_command(command) - assert(type(command) == "table","command object expected") - local purified_name = purify.purify_escapes(command.name) - if self.command_pool[purified_name] then - local command = self.command_pool[purified_name] - --not exactly optimal, but lists are lists. can't do much about them. - table_utils.remove_value(self.command_meta.plugins[command.parent.name],command.name) - if #self.command_meta.plugins[command.parent.name] == 0 then - self.command_meta.plugins[command.parent.name] = nil + assert(type(command) == "table","command object expected") + local purified_name = purify.purify_escapes(command.name) + if self.command_pool[purified_name] then + local command = self.command_pool[purified_name] + --not exactly optimal, but lists are lists. can't do much about them. + table_utils.remove_value(self.command_meta.plugins[command.parent.name],command.name) + if #self.command_meta.plugins[command.parent.name] == 0 then + self.command_meta.plugins[command.parent.name] = nil + end + table_utils.remove_value(self.command_meta.categories[command.options.category],command.name) + if #self.command_meta.categories[command.options.category] == 0 then + self.command_meta.categories[command.options.category] = nil + end + self.command_pool[purified_name] = nil + return true + else + return false end - table_utils.remove_value(self.command_meta.categories[command.options.category],command.name) - if #self.command_meta.categories[command.options.category] == 0 then - self.command_meta.categories[command.options.category] = nil - end - self.command_pool[purified_name] = nil - return true - else - return false - end end function command_handler:get_command(name) - local purified_name = purify.purify_escapes(assert(type(name) == "string") and name) - if self.command_pool[purified_name] then - return self.command_pool[purified_name] - else - return false - end + local purified_name = purify.purify_escapes(assert(type(name) == "string") and name) + if self.command_pool[purified_name] then + return self.command_pool[purified_name] + else + return false + end end function command_handler:get_commands(name) - local list = {} - for k,v in pairs(self.command_pool) do - table.insert(list,k) - end - return list + local list = {} + for k,v in pairs(self.command_pool) do + table.insert(list,k) + end + return list end function command_handler:get_commands_metadata() - return table_utils.deepcopy(self.command_meta) + return table_utils.deepcopy(self.command_meta) end function command_handler:handle(message) - for name,command in pairs(self.command_pool) do - if command.options.regex then - if message.content:match(command.options.regex) then - command:exec(message) - return - end - else - if command.options.prefix then - for _,prefix in pairs(self.prefixes) do - if message.content:match("^"..prefix..name.."$") or message.content:match("^"..prefix..name.."%s") then - command:exec(message) - return - end + for name,command in pairs(self.command_pool) do + if command.options.regex then + if message.content:match(command.options.regex) then + command:exec(message) + return + end + else + if command.options.prefix then + for _,prefix in pairs(self.prefixes) do + if message.content:match("^"..prefix..name.."$") or message.content:match("^"..prefix..name.."%s") then + command:exec(message) + return + end + end + else + if message.content:match("^"..name.."$") or message.content:match("^"..name.."%s") then + command:exec(message) + return + end + end end - else - if message.content:match("^"..name.."$") or message.content:match("^"..name.."%s") then - command:exec(message) - return - end - end end - end end return command_handler diff --git a/libraries/classes/command.lua b/libraries/classes/command.lua index a22567b..13902f5 100644 --- a/libraries/classes/command.lua +++ b/libraries/classes/command.lua @@ -7,178 +7,162 @@ local command = class("Command") local acl = import("classes.command-acl") local discordia = import("discordia") function command:__init(name,callback) - self.rules = acl() - self.name = name - self.timer = discordia.Date():toMilliseconds() - self.options = { - allow_bots = false, --allow bots to execute the command - typing_decorator = false, --set if the bot should be "typing" while the command executes - category = "None", --set category for the command - prefix = true, --if true and if regex isn't enabled, check for prefix at the start. if not, don't check for prefix - regex = false, --check if the message matches this regular expression (should be a string) - no_parsing = false, --check if you want to disable the message argument parsing process - timeout = 1000, --set the timeout for a command - } - if type(callback) == "table" then - for k,v in pairs(callback.options or {}) do - self.options[k] = v + assert(name:match("^[-_%w]+$"),"Name can only contain alphanumeric characters, underscores or dashes") + self.rules = acl() + self.name = name + self.timer = discordia.Date():toMilliseconds() + self.options = { + allow_bots = false, --allow bots to execute the command + typing_decorator = false, --set if the bot should be "typing" while the command executes + category = "None", --set category for the command + prefix = true, --if true check for prefix at the start. if not, don't check for prefix + no_parsing = false, --check if you want to disable the message argument parsing process + timeout = 1000, --set the timeout for a command + } + if type(callback) == "table" then + for k,v in pairs(callback.options or {}) do + self.options[k] = v + end + self.callback = callback.exec + self.args = callback.args or self.args + if callback.users then + for k,v in pairs(callback.users) do + self.rules:set_user_rule(k,v) + end + end + if callback.roles then + for k,v in pairs(callback.roles) do + self.rules:set_group_rule(k,v) + end + end + callback.perms = callback.perms and self.rules:set_perm_rules(callback.perms) + callback.help = callback.help and self:set_help(callback.help,callback.usage) + elseif type(callback) == "function" then + self.callback = callback end - self.callback = callback.exec - self.args = callback.args or self.args - if callback.users then - for k,v in pairs(callback.users) do - self.rules:set_user_rule(k,v) - end - end - if callback.roles then - for k,v in pairs(callback.roles) do - self.rules:set_group_rule(k,v) - end - end - if callback.perms then - self.rules:set_perm_rules(callback.perms) - end - if callback.help then - self:set_help(callback.help,callback.usage) - end - elseif type(callback) == "function" then - self.callback = callback - end end --set the callback to be called on comm:exec(msg) function command:set_callback(fn) - assert(type(fn) == "function","function expected, got "..type(fn)) - self.callback = fn - return self + assert(type(fn) == "function","function expected, got "..type(fn)) + self.callback = fn + return self end --generate help using only description and usage, or nothing at all function command:generate_help(description,usage) - assert(not description or (type(description) == "string"),"Description should be either string or nil, got "..type(description)) - assert(not usage or (type(usage) == "string"),"Usage should be either string or nil, got "..type(usage)) - local backup_usage_str - if self.args then - backup_usage_str = self.name.." <"..table.concat(self.args,"> <")..">" - else - backup_usage_str = "not defined" - end - local permissions = table.concat(self.rules:export_snapshot()["perms"] or {},"\n") - if permissions == "" then - permissions = "All" - end - self.help = {embed = { - title = "Help for ``"..self.name.."``", - description = description, - fields = { - {name = "Usage: ",value = usage or backup_usage_str}, - {name = "Perms: ",value = permissions} - } - }} - return self + assert(not description or (type(description) == "string"),"Description should be either string or nil, got "..type(description)) + assert(not usage or (type(usage) == "string"),"Usage should be either string or nil, got "..type(usage)) + local backup_usage_str + if self.args then + backup_usage_str = self.name.." <"..table.concat(self.args,"> <")..">" + else + backup_usage_str = "not defined" + end + local permissions = table.concat(self.rules:export_snapshot()["perms"] or {"All"},"\n") + self.help = {embed = { + title = "Help for ``"..self.name.."``", + description = description, + fields = { + {name = "Usage: ",value = usage or backup_usage_str}, + {name = "Perms: ",value = permissions} + } + }} + return self end --set the help message to be sent function command:set_help(obj,usage) - if type(obj) == "string" then - self:generate_help(obj,usage) - elseif type(obj) == "table" then - self.help = obj - else - error("Type "..type(obj).." cannot be set as a help message") - end - return self + if type(obj) == "table" then + self.help = obj + else + self:generate_help(obj, + (type(usage) == "string" and usage) + or "No description provided.") + end + return self end --print the help message, or generate it if there is none function command:get_help() - if not self.help then - self:generate_help("Description not defined") - end - return self.help + if not self.help then + self:generate_help("Description not defined") + end + return self.help end function command:set_timeout_callback(fn) - assert(type(fn) == "function","function expected, got "..type(fn)) - self.timeout_callback = fn - return self + assert(type(fn) == "function","function expected, got "..type(fn)) + self.timeout_callback = fn + return self end --check the permissions for command function command:check_permissions(message) - if message.author.bot and (not self.options.allow_bots) then - return false - end - if discordia.Date():toMilliseconds()-self.options.timeout < self.timer then - if self.timeout_callback then - self.timeout_callback(fn) - return false + if message.author.bot and (not self.options.allow_bots) then + return false end - end - self.timer = discordia.Date():toMilliseconds() - if self.rules:check_user(message.author.id) then - local found,allow = self.rules:check_user(message.author.id) - return allow - end - if self.rules:check_group(message.member.roles) then - local found,allow = self.rules:check_group(message.member.roles) - return allow - end - return self.rules:check_perm(message.member:getPermissions(message.channel)) + if discordia.Date():toMilliseconds()-self.options.timeout < self.timer then + if self.timeout_callback then + self.timeout_callback(message) + return false + end + end + self.timer = discordia.Date():toMilliseconds() + if self.rules:check_user(tostring(message.author.id)) then + local found,allow = self.rules:check_user(tostring(message.author.id)) + return allow + end + if self.rules:check_group(message.member.roles) then + local found,allow = self.rules:check_group(message.member.roles) + return allow + end + return self.rules:check_perm(message.member:getPermissions(message.channel)) end --the main entry point for the command - execute the callback within after --multiple checks function command:exec(message,args,opts) - local exec = self.callback - if not self.callback then - error("Callback not set for command "..self.name) - end - if self.decorator then - self.callback = self.decorator(self.callback) - end - local content - if self.options.regex then - content = message.content - else + local exec = self.callback + if not self.callback then + error("Callback not set for command "..self.name) + end + if self.decorator then + self.callback = self.decorator(self.callback) + end local strstart,strend = message.content:find(self.name,1,true) content = message.content:sub(strend+1,-1) - end - if self:check_permissions(message) then - if self.options.typing_decorator then - message.channel:broadcastTyping() - end - local status,args,opts,err = import("air").parse(content,self.args,message.client,message.guild.id) - if status then - local callst,status,response = pcall(self.callback,message,args,opts) - if callst then - if type(status) == "boolean" then - if status then - message:addReaction("✅") - else - message:addReaction("❌") - end + if self:check_permissions(message) then + if self.options.typing_decorator then + message.channel:broadcastTyping() end - else - message:addReaction("⚠️") - message:reply("An internal error occured: "..status) - end - else - message:addReaction("❌") - message:reply(err) + local status,args,opts,err = import("air").parse(content,self.args,message.client,message.guild.id) + if status then + local callst,status,response = pcall(self.callback,message,args,opts) + if callst then + if type(status) == "boolean" then + message:addReaction((status and "✅") or "❌") + end + return + end + message:addReaction("⚠️") + message:reply("An internal error occured: "..status) + return + end + message:addReaction("❌") + message:reply(err) + return end - else message:addReaction("❌") - end end --add decorators for the callback function command:set_decorator(fn) - assert(type(fn) == "function","a decorator function expected, got "..type(fn)) - self.decorator = fn - return self + assert(type(fn) == "function","a decorator function expected, got "..type(fn)) + self.decorator = fn + return self end --get a list of all properties of the command function command:get_properties() - return { - name = self.name, - category = self.options.category, - args = table_utils.deepcopy(self.args), - help = table_utils.deepcopy(self.help), - prefix = self.prefix - } + return { + name = self.name, + category = self.options.category, + args = table_utils.deepcopy(self.args), + help = table_utils.deepcopy(self.help), + prefix = self.prefix + } end return command diff --git a/libraries/classes/emitter-proxy.lua b/libraries/classes/emitter-proxy.lua index d002851..33b708b 100644 --- a/libraries/classes/emitter-proxy.lua +++ b/libraries/classes/emitter-proxy.lua @@ -2,94 +2,94 @@ local class = import("classes.baseclass") local emitter_proxy = class("EmitterProxy") function emitter_proxy:__init(emitter) - self.original = emitter - self.callback_pool = {} + self.original = emitter + self.callback_pool = {} end function emitter_proxy:on(event,callback) - if not self.callback_pool[event] then - self.callback_pool[event] = {} - end - self.callback_pool[event][callback] = callback - self.original:on(event,callback) - return callback + if not self.callback_pool[event] then + self.callback_pool[event] = {} + end + self.callback_pool[event][callback] = callback + self.original:on(event,callback) + return callback end function emitter_proxy:once(event,callback) - if not self.callback_pool[event] then - self.callback_pool[event] = {} - end - local wrapper = function(...) - callback(...) - self.callback_pool[event][callback] = nil - end - self.callback_pool[event][callback] = wrapper - self.callback_pool[event][wrapper] = wrapper - self.original:once(event,wrapper) - return callback + if not self.callback_pool[event] then + self.callback_pool[event] = {} + end + local wrapper = function(...) + callback(...) + self.callback_pool[event][callback] = nil + end + self.callback_pool[event][callback] = wrapper + self.callback_pool[event][wrapper] = wrapper + self.original:once(event,wrapper) + return callback end function emitter_proxy:removeListener(event,callback) - if self.callback_pool[event] and self.callback_pool[event][callback] then - self.callback_pool[event][callback] = nil - self.original:removeListener(event,callback) - end + if self.callback_pool[event] and self.callback_pool[event][callback] then + self.callback_pool[event][callback] = nil + self.original:removeListener(event,callback) + end end function emitter_proxy:removeAllListeners(event,callback) - if self.callback_pool[event] then - for k,v in pairs(self.callback_pool[event]) do - self.original:removeListener(event,v) + if self.callback_pool[event] then + for k,v in pairs(self.callback_pool[event]) do + self.original:removeListener(event,v) + end + self.callback_pool[event] = nil end - self.callback_pool[event] = nil - end end function emitter_proxy:listeners(event) - local copy = {} - if self.callback_pool[event] then - for k,v in pairs(self.callback_pool[event]) do - table.insert(copy,v) + local copy = {} + if self.callback_pool[event] then + for k,v in pairs(self.callback_pool[event]) do + table.insert(copy,v) + end end - end - return copy + return copy end function emitter_proxy:listenerCount(event) - local count = 0 - if event then - if self.callback_pool[event] then - for k,v in pairs(self.callback_pool[event]) do - count = count + 1 - end + local count = 0 + if event then + if self.callback_pool[event] then + for k,v in pairs(self.callback_pool[event]) do + count = count + 1 + end + end + else + for k,v in pairs(self.callback_pool) do + for k2,v2 in pairs(v) do + count = count + 1 + end + end end - else - for k,v in pairs(self.callback_pool) do - for k2,v2 in pairs(v) do - count = count + 1 - end - end - end - return count + return count end function emitter_proxy:propogate(event,emitter) - if not self.callback_pool[event] then - self.callback_pool[event] = {} - end - local emitter_propogate_handler = function(...) - emitter:emit(event,...) - end - self.callback_pool[event][emitter_propogate_handler] = emitter_propogate_handler - self.original:on(event,emitter_propogate_handler) - return emitter_propogate_handler + if not self.callback_pool[event] then + self.callback_pool[event] = {} + end + local emitter_propogate_handler = function(...) + emitter:emit(event,...) + end + self.callback_pool[event][emitter_propogate_handler] = emitter_propogate_handler + self.original:on(event,emitter_propogate_handler) + return emitter_propogate_handler end function emitter_proxy:destroy() - for k,v in pairs(self.callback_pool) do - for k2,v2 in pairs(v) do - self.original:removeListener(k,v2) + for k,v in pairs(self.callback_pool) do + for k2,v2 in pairs(v) do + self.original:removeListener(k,v2) + end end - end end return emitter_proxy diff --git a/libraries/classes/interfaces/discord.lua b/libraries/classes/interfaces/discord.lua deleted file mode 100644 index fa0c455..0000000 --- a/libraries/classes/interfaces/discord.lua +++ /dev/null @@ -1,12 +0,0 @@ -local interface = {} -interface.wrapper = function(client,guild_id) - local new_i = {} - new_i.message = {} - new_i.message.get = function(channel,id) - local new_m = {} - local message = client.getMessage(id) - local new_m.content = message.content - local new_m.created_at = message.createdAt - local new_m.attachments = {} - for k,v in pairs(message.attachments) do - table.insert(new_m diff --git a/libraries/classes/monitor.lua b/libraries/classes/monitor.lua deleted file mode 100644 index 9548f63..0000000 --- a/libraries/classes/monitor.lua +++ /dev/null @@ -1,90 +0,0 @@ -local RPC_server = import("classes.RPC-server") -local class = import("classes.baseclass") -local monitor = class("Monitor") - ---we only generate proxies as far as 1 object deep. ---to provide seamlessness, metamethods that request object proxies from their ---pointers may be used on the client side - ---pointers here mean tables that contain the __id and __type properties. ---they do not hold any info on the object besides its class name and id - ---a lookup table of all classes that we do not ignore. we exclude client and containers ---because they might break the sandboxing. we *do not* want that. -local allowed_types = { - ["guild"] = true, - ["member"] = true, - ["emoji"] = true, - ["message"] = true, - ["channel"] = true, - ["role"] = true, - ["user"] = true, - ["invite"] = true, - ["guildtextchannel"] = true, - ["textchannel"] = true, - ["iterable"] = true, - ["cache"] = true, - ["arrayiterable"] = true, - ["filteretediterable"] = true, - ["secondarycache"] = true, - ["weakcache"] = true, - ["tableiterable"] = true, -} - ---a lookup table of classes that can be explicitly converted to arrays. -local iterable_types = { - ["iterable"] = true, - ["cache"] = true, - ["arrayiterable"] = true, - ["filteretediterable"] = true, - ["secondarycache"] = true, - ["weakcache"] = true, - ["tableiterable"] = true, -} - -local comprehend_object = function(object) - local output - if (type(object) == "table") and (object.__class) then - --our object is an instance of a class - local class = object.__class.__name:lower() - if allowed_types[class] and (not iterable_types[class]) then - --our object can only be pointed to - output = {__id = object[k].id, __type = class} - else - --our object can be converted to an array - - end - else - --our object is either an atomic data type, a string or a table. - - end -end - -local create_proxy = function(object) - local output = {} - for k,v in pairs(getmetatable(object).__getters) do - end -end - -local proto_api = { - msg = { - get = function(channel,id) - channel:getMessage(id) - end, - }, - guild = { - - }, - member = { - - }, - channel = { - - } -} - -function monitor:__init(guild,options) - assert(guild,"No guild provided") - assert(options,"No options provided (arg 2)") - - diff --git a/libraries/classes/plugin-handler.lua b/libraries/classes/plugin-handler.lua index 2348e1d..6084127 100644 --- a/libraries/classes/plugin-handler.lua +++ b/libraries/classes/plugin-handler.lua @@ -12,130 +12,130 @@ local core = import("core") local emitter_proxy = import("classes.emitter-proxy") local table_utils = import("table-utils") function plugin_handler:__init(parent_server) - assert(parent_server,"server handler to assign the plugin handler to has not been provided") - self.server_handler = parent_server - self.plugins = {} - self.plugin_info = {} - self.plugin_paths = {} - self.server_handler.event_emitter:on("serverSaveConfig",function() - print("[SERVER] Saving plugins configs") - for name,plugin in pairs(self.plugins) do - self:save_plugin_config(name) - end - end) + assert(parent_server,"server handler to assign the plugin handler to has not been provided") + self.server_handler = parent_server + self.plugins = {} + self.plugin_info = {} + self.plugin_paths = {} + self.server_handler.event_emitter:on("serverSaveConfig",function() + print("[SERVER] Saving plugins configs") + for name,plugin in pairs(self.plugins) do + self:save_plugin_config(name) + end + end) end function plugin_handler:load_plugin_config(name) - return file.readJSON(self.server_handler.config_path..name..".json",{}) + return file.readJSON(self.server_handler.config_path..name..".json",{}) end function plugin_handler:save_plugin_config(name) - if self.plugins[name] then - file.writeJSON(self.server_handler.config_path..name..".json",self.plugins[name].__env.config) - end + if self.plugins[name] then + file.writeJSON(self.server_handler.config_path..name..".json",self.plugins[name].__env.config) + end end function plugin_handler:add_plugin_folder(path) - assert(type(path) == "string","path should be a string, got "..type(path)) - table.insert(self.plugin_paths,path) + assert(type(path) == "string","path should be a string, got "..type(path)) + table.insert(self.plugin_paths,path) end function plugin_handler:scan_folder(path) - local file = io.open(path.."/meta.json","r") - if file then - local metadata,code,err = json.decode(file:read("*a")) - if metadata and metadata.name then - self.plugin_info[metadata.name] = metadata - self.plugin_info[metadata.name].path = path.."/" - self.plugin_info[metadata.name].loaded = false - end - file:close() - else - for k,v in pairs({"/init.lua","/main.lua"}) do - local file = io.open(path..v,"r") - if file then - local name = path:match("[^/]+$") - self.plugin_info[name] = {["main"]=v:gsub("/","")} - self.plugin_info[name].path = path.."/" - self.plugin_info[name].loaded = false + local file = io.open(path.."/meta.json","r") + if file then + local metadata,code,err = json.decode(file:read("*a")) + if metadata and metadata.name then + self.plugin_info[metadata.name] = metadata + self.plugin_info[metadata.name].path = path.."/" + self.plugin_info[metadata.name].loaded = false + end file:close() - end + else + for k,v in pairs({"/init.lua","/main.lua"}) do + local file = io.open(path..v,"r") + if file then + local name = path:match("[^/]+$") + self.plugin_info[name] = {["main"]=v:gsub("/","")} + self.plugin_info[name].path = path.."/" + self.plugin_info[name].loaded = false + file:close() + end + end end - end end function plugin_handler:update_plugin_info() - for k,v in pairs(self.plugin_paths) do - if file.existsDir(v) then - file.ls(v):gsub("[^\n]+",function(c) - self:scan_folder(v..c) - end) + for k,v in pairs(self.plugin_paths) do + if file.existsDir(v) then + file.ls(v):gsub("[^\n]+",function(c) + self:scan_folder(v..c) + end) + end end - end end function plugin_handler:list_loadable() - return table_utils.deepcopy(self.plugin_info) + return table_utils.deepcopy(self.plugin_info) end function plugin_handler:load(name) - if not self.plugin_info[name] then - return false, "No such plugin" - end - if not self.plugin_info[name].main then - return false, "Plugin metadata entry doesn't specify the main file path or main file isn't found" - end - if self.plugin_info[name].loaded then - return false, "Plugin is already loaded" - end - local environment = setmetatable({ - id = self.server_handler.id, - globals = self.server_handler.config, - signals = emitter_proxy(self.server_handler.signal_emitter), - client = self.server_handler.client, - events = emitter_proxy(self.server_handler.event_emitter), - discordia = import("discordia"), - server = self.server_handler, - command_handler = self.server_handler.command_handler, - plugin_handler = self.server_handler.plugin_handler, - log = function() end, - config = self:load_plugin_config(name), - import = import, - },{__index = _G}) - local plugin_meta = self.plugin_info[name] - if file.exists(plugin_meta.path..plugin_meta.main) then - environment["plugin_path"] = plugin_meta.path - local plugin_content = file.read(plugin_meta.path..plugin_meta.main,"*a") - local plugin_loader,err = load(plugin_content,"plugin loader: "..plugin_meta.path..plugin_meta.main,nil,environment) - if plugin_loader then - local plugin_object = plugin_loader() - if plugin_object then - plugin_object.name = name - plugin_object:load(environment) - self.plugins[name] = plugin_object - self.plugins[name].__env = environment - self.plugin_info[name].loaded = true - return true - else - return false, "Plugin object missing" - end - else - return false, err + if not self.plugin_info[name] then + return false, "No such plugin" + end + if not self.plugin_info[name].main then + return false, "Plugin metadata entry doesn't specify the main file path or main file isn't found" + end + if self.plugin_info[name].loaded then + return false, "Plugin is already loaded" + end + local environment = setmetatable({ + id = self.server_handler.id, + globals = self.server_handler.config, + signals = emitter_proxy(self.server_handler.signal_emitter), + client = self.server_handler.client, + events = emitter_proxy(self.server_handler.event_emitter), + discordia = import("discordia"), + server = self.server_handler, + command_handler = self.server_handler.command_handler, + plugin_handler = self.server_handler.plugin_handler, + log = function() end, + config = self:load_plugin_config(name), + import = import, + },{__index = _G}) + local plugin_meta = self.plugin_info[name] + if file.exists(plugin_meta.path..plugin_meta.main) then + environment["plugin_path"] = plugin_meta.path + local plugin_content = file.read(plugin_meta.path..plugin_meta.main,"*a") + local plugin_loader,err = load(plugin_content,"plugin loader: "..plugin_meta.path..plugin_meta.main,nil,environment) + if plugin_loader then + local plugin_object = plugin_loader() + if plugin_object then + plugin_object.name = name + plugin_object:load(environment) + self.plugins[name] = plugin_object + self.plugins[name].__env = environment + self.plugin_info[name].loaded = true + return true + else + return false, "Plugin object missing" + end + else + return false, err + end + else + return false, "File specified as the main file is inaccessible" end - else - return false, "File specified as the main file is inaccessible" - end end function plugin_handler:unload(name) - if self.plugins[name] then - self.plugins[name].__env.signals:destroy() - self.plugins[name].__env.events:destroy() - self.plugins[name]:unload() - self.plugin_info[name].loaded = false - return true - else - return false,"Plugin is not loaded" - end + if self.plugins[name] then + self.plugins[name].__env.signals:destroy() + self.plugins[name].__env.events:destroy() + self.plugins[name]:unload() + self.plugin_info[name].loaded = false + return true + else + return false,"Plugin is not loaded" + end end return plugin_handler diff --git a/libraries/classes/plugin.lua b/libraries/classes/plugin.lua index 625ff23..fc6f760 100644 --- a/libraries/classes/plugin.lua +++ b/libraries/classes/plugin.lua @@ -2,58 +2,58 @@ local class = import("classes.baseclass") local plugin = class("Plugin") function plugin:__init() - self.command_pool = {} - self.config = {} + self.command_pool = {} + self.config = {} end function plugin:load(environment) - self.command_handler = environment.server.command_handler - for k,v in pairs(self.command_pool) do - self.command_handler:add_command(v) - end + self.command_handler = environment.server.command_handler + for k,v in pairs(self.command_pool) do + self.command_handler:add_command(v) + end end function plugin:unload() - if self.removal_callback then - self.removal_callback() - end - for k,v in pairs(self.command_pool) do - self.command_handler:remove_command(v) - end + if self.removal_callback then + self.removal_callback() + end + for k,v in pairs(self.command_pool) do + self.command_handler:remove_command(v) + end end function plugin:for_all_commands(fn) - assert(type(fn)=="function","function expected, got "..type(fn)) - for k,v in pairs(self.command_pool) do - fn(v) - end + assert(type(fn)=="function","function expected, got "..type(fn)) + for k,v in pairs(self.command_pool) do + fn(v) + end end function plugin:for_every_new_command(fn) - assert(type(fn)=="function","function expected, got "..type(fn)) - self.decorator = fn + assert(type(fn)=="function","function expected, got "..type(fn)) + self.decorator = fn end function plugin:add_command(command_object) - if self.decorator then - self.fn(command_object) - end - command_object.parent = self - self.command_pool[command_object] = command_object - --in post init state: we request the command handler to add the commands - --that way, we can link our plugin back to the command handler - if self.command_handler then - self.command_handler:add_command(command_object) - end + if self.decorator then + self.fn(command_object) + end + command_object.parent = self + self.command_pool[command_object] = command_object + --in post init state: we request the command handler to add the commands + --that way, we can link our plugin back to the command handler + if self.command_handler then + self.command_handler:add_command(command_object) + end end function plugin:remove_command(command_object) - if self.command_pool[command_object] then - self.command_pool[command_object] = nil - end - --remove command after post-init state - if self.command_handler then - self.command_handler:remove_command(command_object) - end + if self.command_pool[command_object] then + self.command_pool[command_object] = nil + end + --remove command after post-init state + if self.command_handler then + self.command_handler:remove_command(command_object) + end end return plugin diff --git a/libraries/classes/server-handler.lua b/libraries/classes/server-handler.lua index 8594861..6441a3d 100644 --- a/libraries/classes/server-handler.lua +++ b/libraries/classes/server-handler.lua @@ -8,96 +8,96 @@ local eventlist = import("eventlist") local discordia = import("discordia") local function check_partitioning(id,...) - args = {...} - v = args[1] - if type(v) == "table" and v.guild and v.guild.id == id then - return true - elseif not (type(v) == "table") then - return true - elseif type(v) == "table" and (not v.guild) and (tostring(v):find("Guild: ")) and v.id == id then - return true - elseif type(v) == "table" and (not v.guild) and (v.message) and (v.message.guild.id == id) then - return true - else - return false - end + args = {...} + v = args[1] + if type(v) == "table" and v.guild and v.guild.id == id then + return true + elseif not (type(v) == "table") then + return true + elseif type(v) == "table" and (not v.guild) and (tostring(v):find("Guild: ")) and v.id == id then + return true + elseif type(v) == "table" and (not v.guild) and (v.message) and (v.message.guild.id == id) then + return true + else + return false + end end function server_handler:__init(client,guild,options) - assert(type(client) == "table","discordia client expected, got "..type(client)) - self.client = client - self.uptime = discordia.Date() - self.event_emitter = core.Emitter:new() - self.signal_emitter = core.Emitter:new() - self.plugin_handler = plugin_handler(self) - self.command_handler = command_handler(self) - self.id = guild.id - --conifgurable properties - self.config_path = options.path or "./servers/%id/" - self.autosave = options.path or true - self.autosave_frequency = options.autosave_frequency or 10 - self.plugin_search_paths = options.plugin_search_paths or {"./plugins/"} - self.default_plugins = options.default_plugins or {"test"} - self.default_prefixes = options.default_prefixes or {"&","<@!"..self.client.user.id..">"} - self.config = {} - self.config_path = self.config_path:gsub("%%id",self.id) - self:load_config() - self.config["prefix"] = self.config["prefix"] or self.default_prefixes[1] or "(missing prefix)" - self.message_counter = 0 - if self.autosave then - self.client:on("messageCreate",function(msg) - self.message_counter = self.message_counter + 1 - if math.fmod(self.message_counter,self.autosave_frequency) == 0 then - self:save_config() - end - end) - end - if not file.existsDir(self.config_path) then - os.execute("mkdir -p "..self.config_path) - end - for k,v in pairs(eventlist) do - self.client:on(v,function(...) - --check if the event is for this server, and then emit. - if check_partitioning(self.id,...) then - self.event_emitter:emit(v,...) - end - end) - end - self.client:on("messageCreate",function(msg) - if msg.guild and msg.guild.id == self.id then - self.command_handler:handle(msg) + assert(type(client) == "table","discordia client expected, got "..type(client)) + self.client = client + self.uptime = discordia.Date() + self.event_emitter = core.Emitter:new() + self.signal_emitter = core.Emitter:new() + self.plugin_handler = plugin_handler(self) + self.command_handler = command_handler(self) + self.id = guild.id + --conifgurable properties + self.config_path = options.path or "./servers/%id/" + self.autosave = options.path or true + self.autosave_frequency = options.autosave_frequency or 10 + self.plugin_search_paths = options.plugin_search_paths or {"./plugins/"} + self.default_plugins = options.default_plugins or {"test"} + self.default_prefixes = options.default_prefixes or {"&","<@!"..self.client.user.id..">"} + self.config = {} + self.config_path = self.config_path:gsub("%%id",self.id) + self:load_config() + self.config["prefix"] = self.config["prefix"] or self.default_prefixes[1] or "(missing prefix)" + self.message_counter = 0 + if self.autosave then + self.client:on("messageCreate",function(msg) + self.message_counter = self.message_counter + 1 + if math.fmod(self.message_counter,self.autosave_frequency) == 0 then + self:save_config() + end + end) + end + if not file.existsDir(self.config_path) then + os.execute("mkdir -p "..self.config_path) + end + for k,v in pairs(eventlist) do + self.client:on(v,function(...) + --check if the event is for this server, and then emit. + if check_partitioning(self.id,...) then + self.event_emitter:emit(v,...) + end + end) + end + self.client:on("messageCreate",function(msg) + if msg.guild and msg.guild.id == self.id then + self.command_handler:handle(msg) + end + end) + for _,path in pairs(self.plugin_search_paths) do + self.plugin_handler:add_plugin_folder(path) + end + self.plugin_handler:update_plugin_info() + for _,plugin_name in pairs(self.default_plugins) do + print("[SERVER] Loading plugin: "..tostring(plugin_name).." - ", self.plugin_handler:load(plugin_name)) + end + for _,prefix in pairs(self.default_prefixes) do + self.command_handler:add_prefix(prefix) end - end) - for _,path in pairs(self.plugin_search_paths) do - self.plugin_handler:add_plugin_folder(path) - end - self.plugin_handler:update_plugin_info() - for _,plugin_name in pairs(self.default_plugins) do - print("[SERVER] Loading plugin: "..tostring(plugin_name).." - ", self.plugin_handler:load(plugin_name)) - end - for _,prefix in pairs(self.default_prefixes) do - self.command_handler:add_prefix(prefix) - end end function server_handler:load_config(path) - print("[SERVER] Loading config") - if path then - self.config = file.readJSON(path,{}) - else - self.config = file.readJSON(self.config_path.."config.json") - end - self.event_emitter:emit("serverLoadConfig",self.config) + print("[SERVER] Loading config") + if path then + self.config = file.readJSON(path,{}) + else + self.config = file.readJSON(self.config_path.."config.json") + end + self.event_emitter:emit("serverLoadConfig",self.config) end function server_handler:save_config(path) - print("[SERVER] Saving config") - if path then - file.writeJSON(path,self.config) - else - file.writeJSON(self.config_path.."config.json",self.config) - end - self.event_emitter:emit("serverSaveConfig",self.config) + print("[SERVER] Saving config") + if path then + file.writeJSON(path,self.config) + else + file.writeJSON(self.config_path.."config.json",self.config) + end + self.event_emitter:emit("serverSaveConfig",self.config) end return server_handler diff --git a/plugins/debug/init.lua b/plugins/debug/init.lua index 6408cf6..c5835a3 100644 --- a/plugins/debug/init.lua +++ b/plugins/debug/init.lua @@ -7,4 +7,29 @@ local save = command("save",{ end }) plugin:add_command(save) +local err = command("error",{ + help = "Force error", + exec = function() + error("Errored successfully!") + end +}) +plugin:add_command(err) +local perm_error = command("permerror",{ + help = "Force permission error", + users = { + ["245973168257368076"] = -1 + }, + exec = function(msg) + msg:reply([[o no he's hot]]) + end +}) +plugin:add_command(perm_error) +local return_error = command("return_error",{ + help = "Force a return value error", + exec = function(msg) + msg:reply("nono :)") + return false + end +}) +plugin:add_command(return_error) return plugin