This commit is contained in:
Yessiest 2022-05-20 21:52:22 +04:00
parent 3dd57d3bdc
commit 38c9e580b9
12 changed files with 645 additions and 738 deletions

View File

@ -6,87 +6,87 @@ local class = import("classes.baseclass")
local table_utils = import("table-utils") local table_utils = import("table-utils")
local acl = class("ACL") local acl = class("ACL")
function acl:__init() function acl:__init()
self.user_rules = {} self.user_rules = {}
self.group_rules = {} self.group_rules = {}
end end
function acl:set_user_rule(user_id,status) function acl:set_user_rule(user_id,status)
assert( assert(
(status == nil) or (status == 0) or (status == -1) or (status == 1), (status == nil) or (status == 0) or (status == -1) or (status == 1),
"invalid status setting" "invalid status setting"
) )
self.user_rules[user_id] = status self.user_rules[user_id] = status
end end
function acl:set_group_rule(group_id,status) function acl:set_group_rule(group_id,status)
assert( assert(
(status == nil) or (status == 0) or (status == -1) or (status == 1), (status == nil) or (status == 0) or (status == -1) or (status == 1),
"invalid status setting" "invalid status setting"
) )
self.group_rules[group_id] = status self.group_rules[group_id] = status
end end
function acl:check_user(user_id) function acl:check_user(user_id)
if self.user_rules[user_id] and self.user_rules[user_id] ~= 0 then if self.user_rules[user_id] and self.user_rules[user_id] ~= 0 then
return true,(self.user_rules[user_id] == 1) return true,(self.user_rules[user_id] == 1)
else else
return false return false
end end
end end
function acl:check_group(groups) function acl:check_group(groups)
local allow = false local allow = false
local found = false local found = false
for k,v in pairs(groups) do for k,v in pairs(groups) do
if self.group_rules[v] then if self.group_rules[v] then
found = true found = true
allow = self.group_rules[v] allow = self.group_rules[v]
end
end end
end return found,(allow and allow == 1)
return found,(allow and allow == 1)
end end
function acl:export_all_lists() function acl:export_all_lists()
local lists = { local lists = {
users = "", users = "",
groups = "" groups = ""
} }
for k,v in pairs(self.user_rules) do for k,v in pairs(self.user_rules) do
lists.users = lists.users..k..":"..tostring(v)..";\n" lists.users = lists.users..k..":"..tostring(v)..";\n"
end end
for k,v in pairs(self.group_rules) do for k,v in pairs(self.group_rules) do
lists.groups = lists.groups..k..":"..tostring(v)..";\n" lists.groups = lists.groups..k..":"..tostring(v)..";\n"
end end
return lists return lists
end end
function acl:export_user_list() function acl:export_user_list()
local list = "" local list = ""
for k,v in pairs(self.user_rules) do for k,v in pairs(self.user_rules) do
list = list..k..":"..tostring(v)..";\n" list = list..k..":"..tostring(v)..";\n"
end end
return list return list
end end
function acl:export_group_list() function acl:export_group_list()
local list = "" local list = ""
for k,v in pairs(self.group_rules) do for k,v in pairs(self.group_rules) do
list = list..k..":"..tostring(v)..";\n" list = list..k..":"..tostring(v)..";\n"
end end
return list return list
end end
function acl:export_snapshot() function acl:export_snapshot()
return { return {
user_rules = bot_utils.deepcopy(self.user_rules), user_rules = bot_utils.deepcopy(self.user_rules),
group_rules = bot_utils.deepcopy(self.group_rules) group_rules = bot_utils.deepcopy(self.group_rules)
} }
end end
function acl:import_snapshot(t) function acl:import_snapshot(t)
self.user_rules = t.user_rules self.user_rules = t.user_rules
self.group_rules = t.group_rules self.group_rules = t.group_rules
end end
function acl:import_user_list(list) function acl:import_user_list(list)
list:gsub("(%w+):(%d+)",function(id,status) list:gsub("(%w+):(%d+)",function(id,status)
self.user_rules[id] = status self.user_rules[id] = status
end) end)
end end
function acl:import_group_list(list) function acl:import_group_list(list)
list:gsub("(%w+):(%d+)",function(id,status) list:gsub("(%w+):(%d+)",function(id,status)
self.group_rules[id] = status self.group_rules[id] = status
end) end)
end end
return acl return acl

View File

@ -1,32 +1,32 @@
--class generator (for the purpose of creating classes) --class generator (for the purpose of creating classes)
return function(name) 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 = {} local new_class = {}
new_class.__classname = name or "Object" new_class.__classname = name or "Object"
new_class.__index = new_class 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 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 end

View File

@ -7,84 +7,84 @@ local enum_perms = discordia.enums.permission
--The following method extends the ACL class to work with rule-specific features, --The following method extends the ACL class to work with rule-specific features,
--such as the role position --such as the role position
function command_acl:check_group(roles) function command_acl:check_group(roles)
local found = false local found = false
local highest_role = nil local highest_role = nil
local highest_role_status = nil local highest_role_status = nil
for k,v in pairs(roles) do for k,v in pairs(roles) do
if self.group_rules[v.id] then if self.group_rules[tostring(v.id)] then
found = true found = true
if not highest_role then if not highest_role then
highest_role = v highest_role = v
highest_role_status = self.group_rules[v.id] highest_role_status = self.group_rules[tostring(v.id)]
end end
if v.position > highest_role.position then if v.position > highest_role.position then
highest_role = v highest_role = v
highest_role_status = self.group_rules[v.id] highest_role_status = self.group_rules[tostring(v.id)]
end end
end
end end
end local allow = highest_role_status
local allow = highest_role_status return found,(allow and allow == 1)
return found,(allow and allow == 1)
end end
--The following methods extend the ACL class to add the "perm" permissions --The following methods extend the ACL class to add the "perm" permissions
--(the fallback when no rule/user permissions are found) --(the fallback when no rule/user permissions are found)
function command_acl:__init() function command_acl:__init()
self.user_rules = {} self.user_rules = {}
self.group_rules = {} self.group_rules = {}
self.perm_rules = {} self.perm_rules = {}
end end
function command_acl:check_perm(perms) function command_acl:check_perm(perms)
local output = true local output = true
for k,v in pairs(self.perm_rules) do for k,v in pairs(self.perm_rules) do
if (bit.band(perms[1],enum_perms[v]) == 0) then if (bit.band(perms[1],enum_perms[v]) == 0) then
output = false output = false
end end
end end
return output return output
end end
function command_acl:set_perm_rules(list) function command_acl:set_perm_rules(list)
assert(type(list)=="table","table expected, got "..type(list)) assert(type(list)=="table","table expected, got "..type(list))
self.perm_rules = list self.perm_rules = list
end end
function command_acl:export_all_lists() function command_acl:export_all_lists()
local lists = { local lists = {
users = "", users = "",
groups = "", groups = "",
perm = "" perm = ""
} }
for k,v in pairs(self.user_rules) do for k,v in pairs(self.user_rules) do
lists.users = lists.users..k..":"..tostring(v)..";\n" lists.users = lists.users..k..":"..tostring(v)..";\n"
end end
for k,v in pairs(self.group_rules) do for k,v in pairs(self.group_rules) do
lists.groups = lists.groups..k..":"..tostring(v)..";\n" lists.groups = lists.groups..k..":"..tostring(v)..";\n"
end end
for k,v in pairs(self.perm_rules) do for k,v in pairs(self.perm_rules) do
lists.perm = lists.perm..k..":"..tostring(v)..";\n" lists.perm = lists.perm..k..":"..tostring(v)..";\n"
end end
return lists return lists
end end
function command_acl:export_perm_list() function command_acl:export_perm_list()
local list = "" local list = ""
for k,v in pairs(self.perm_rules) do for k,v in pairs(self.perm_rules) do
list = list..k..":"..tostring(v)..";\n" list = list..k..":"..tostring(v)..";\n"
end end
return list return list
end end
function command_acl:export_snapshot() function command_acl:export_snapshot()
return { return {
user_rules = table_utils.deepcopy(self.user_rules), user_rules = table_utils.deepcopy(self.user_rules),
group_rules = table_utils.deepcopy(self.group_rules), group_rules = table_utils.deepcopy(self.group_rules),
perm_rules = table_utils.deepcopy(self.perm_rules) perm_rules = table_utils.deepcopy(self.perm_rules)
} }
end end
function command_acl:import_snapshot(t) function command_acl:import_snapshot(t)
self.user_rules = t.user_rules self.user_rules = t.user_rules
self.group_rules = t.group_rules self.group_rules = t.group_rules
self.perm_rules = t.perm_rules self.perm_rules = t.perm_rules
end end
function command_acl:import_perm_list() function command_acl:import_perm_list()
list:gsub("(%w+):(%d+)",function(id,status) list:gsub("(%w+):(%d+)",function(id,status)
self.perm_rules[id] = status self.perm_rules[id] = status
end) end)
end end
return command_acl return command_acl

View File

@ -9,108 +9,108 @@ local command_handler = class("Command-handler")
local table_utils = import("table-utils") local table_utils = import("table-utils")
local purify = import("purify") local purify = import("purify")
function command_handler:__init(parent_server) function command_handler:__init(parent_server)
self.server_handler = assert(parent_server,"parent server handler not provided") self.server_handler = assert(parent_server,"parent server handler not provided")
self.command_pool = {} self.command_pool = {}
self.prefixes = {} self.prefixes = {}
self.command_meta = { self.command_meta = {
plugins = {}, plugins = {},
categories = {} categories = {}
} }
end end
function command_handler:add_prefix(prefix) function command_handler:add_prefix(prefix)
local purified_prefix = purify.purify_escapes(prefix) local purified_prefix = purify.purify_escapes(prefix)
self.prefixes[purified_prefix] = purified_prefix self.prefixes[purified_prefix] = purified_prefix
return true return true
end end
function command_handler:remove_prefix(prefix) function command_handler:remove_prefix(prefix)
local purified_prefix = purify.purify_escapes(prefix) local purified_prefix = purify.purify_escapes(prefix)
if self.prefixes[purified_prefix] or table_utils.count(self.prefixes) <= 1 then if self.prefixes[purified_prefix] or table_utils.count(self.prefixes) <= 1 then
self.prefix[purified_prefix] = nil self.prefix[purified_prefix] = nil
return true return true
else else
return false, ( return false, (
(self.prefixes[purified_prefix] and "No such prefix") or (self.prefixes[purified_prefix] and "No such prefix") or
"Cannot remove the last remaining prefix" "Cannot remove the last remaining prefix"
) )
end end
end end
function command_handler:get_prefixes() function command_handler:get_prefixes()
return table_utils.deepcopy(self.prefixes) return table_utils.deepcopy(self.prefixes)
end end
function command_handler:add_command(command) function command_handler:add_command(command)
assert(type(command) == "table","command object expected") assert(type(command) == "table","command object expected")
local purified_name = purify.purify_escapes(command.name) local purified_name = purify.purify_escapes(command.name)
self.command_pool[purified_name] = command self.command_pool[purified_name] = command
if not self.command_meta.plugins[command.parent.name] then if not self.command_meta.plugins[command.parent.name] then
self.command_meta.plugins[command.parent.name] = {} self.command_meta.plugins[command.parent.name] = {}
end end
if not self.command_meta.categories[command.options.category] then if not self.command_meta.categories[command.options.category] then
self.command_meta.categories[command.options.category] = {} self.command_meta.categories[command.options.category] = {}
end end
table.insert(self.command_meta.categories[command.options.category],command.name) table.insert(self.command_meta.categories[command.options.category],command.name)
table.insert(self.command_meta.plugins[command.parent.name],command.name) table.insert(self.command_meta.plugins[command.parent.name],command.name)
return command return command
end end
function command_handler:remove_command(command) function command_handler:remove_command(command)
assert(type(command) == "table","command object expected") assert(type(command) == "table","command object expected")
local purified_name = purify.purify_escapes(command.name) local purified_name = purify.purify_escapes(command.name)
if self.command_pool[purified_name] then if self.command_pool[purified_name] then
local command = self.command_pool[purified_name] local command = self.command_pool[purified_name]
--not exactly optimal, but lists are lists. can't do much about them. --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) table_utils.remove_value(self.command_meta.plugins[command.parent.name],command.name)
if #self.command_meta.plugins[command.parent.name] == 0 then if #self.command_meta.plugins[command.parent.name] == 0 then
self.command_meta.plugins[command.parent.name] = nil 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 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 end
function command_handler:get_command(name) function command_handler:get_command(name)
local purified_name = purify.purify_escapes(assert(type(name) == "string") and name) local purified_name = purify.purify_escapes(assert(type(name) == "string") and name)
if self.command_pool[purified_name] then if self.command_pool[purified_name] then
return self.command_pool[purified_name] return self.command_pool[purified_name]
else else
return false return false
end end
end end
function command_handler:get_commands(name) function command_handler:get_commands(name)
local list = {} local list = {}
for k,v in pairs(self.command_pool) do for k,v in pairs(self.command_pool) do
table.insert(list,k) table.insert(list,k)
end end
return list return list
end end
function command_handler:get_commands_metadata() function command_handler:get_commands_metadata()
return table_utils.deepcopy(self.command_meta) return table_utils.deepcopy(self.command_meta)
end end
function command_handler:handle(message) function command_handler:handle(message)
for name,command in pairs(self.command_pool) do for name,command in pairs(self.command_pool) do
if command.options.regex then if command.options.regex then
if message.content:match(command.options.regex) then if message.content:match(command.options.regex) then
command:exec(message) command:exec(message)
return return
end end
else else
if command.options.prefix then if command.options.prefix then
for _,prefix in pairs(self.prefixes) do for _,prefix in pairs(self.prefixes) do
if message.content:match("^"..prefix..name.."$") or message.content:match("^"..prefix..name.."%s") then if message.content:match("^"..prefix..name.."$") or message.content:match("^"..prefix..name.."%s") then
command:exec(message) command:exec(message)
return 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
else
if message.content:match("^"..name.."$") or message.content:match("^"..name.."%s") then
command:exec(message)
return
end
end
end end
end
end end
return command_handler return command_handler

View File

@ -7,178 +7,162 @@ local command = class("Command")
local acl = import("classes.command-acl") local acl = import("classes.command-acl")
local discordia = import("discordia") local discordia = import("discordia")
function command:__init(name,callback) function command:__init(name,callback)
self.rules = acl() assert(name:match("^[-_%w]+$"),"Name can only contain alphanumeric characters, underscores or dashes")
self.name = name self.rules = acl()
self.timer = discordia.Date():toMilliseconds() self.name = name
self.options = { self.timer = discordia.Date():toMilliseconds()
allow_bots = false, --allow bots to execute the command self.options = {
typing_decorator = false, --set if the bot should be "typing" while the command executes allow_bots = false, --allow bots to execute the command
category = "None", --set category for the command typing_decorator = false, --set if the bot should be "typing" while the command executes
prefix = true, --if true and if regex isn't enabled, check for prefix at the start. if not, don't check for prefix category = "None", --set category for the command
regex = false, --check if the message matches this regular expression (should be a string) 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 no_parsing = false, --check if you want to disable the message argument parsing process
timeout = 1000, --set the timeout for a command timeout = 1000, --set the timeout for a command
} }
if type(callback) == "table" then if type(callback) == "table" then
for k,v in pairs(callback.options or {}) do for k,v in pairs(callback.options or {}) do
self.options[k] = v 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 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 end
--set the callback to be called on comm:exec(msg) --set the callback to be called on comm:exec(msg)
function command:set_callback(fn) function command:set_callback(fn)
assert(type(fn) == "function","function expected, got "..type(fn)) assert(type(fn) == "function","function expected, got "..type(fn))
self.callback = fn self.callback = fn
return self return self
end end
--generate help using only description and usage, or nothing at all --generate help using only description and usage, or nothing at all
function command:generate_help(description,usage) 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 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)) assert(not usage or (type(usage) == "string"),"Usage should be either string or nil, got "..type(usage))
local backup_usage_str local backup_usage_str
if self.args then if self.args then
backup_usage_str = self.name.." <"..table.concat(self.args,"> <")..">" backup_usage_str = self.name.." <"..table.concat(self.args,"> <")..">"
else else
backup_usage_str = "not defined" backup_usage_str = "not defined"
end end
local permissions = table.concat(self.rules:export_snapshot()["perms"] or {},"\n") local permissions = table.concat(self.rules:export_snapshot()["perms"] or {"All"},"\n")
if permissions == "" then self.help = {embed = {
permissions = "All" title = "Help for ``"..self.name.."``",
end description = description,
self.help = {embed = { fields = {
title = "Help for ``"..self.name.."``", {name = "Usage: ",value = usage or backup_usage_str},
description = description, {name = "Perms: ",value = permissions}
fields = { }
{name = "Usage: ",value = usage or backup_usage_str}, }}
{name = "Perms: ",value = permissions} return self
}
}}
return self
end end
--set the help message to be sent --set the help message to be sent
function command:set_help(obj,usage) function command:set_help(obj,usage)
if type(obj) == "string" then if type(obj) == "table" then
self:generate_help(obj,usage) self.help = obj
elseif type(obj) == "table" then else
self.help = obj self:generate_help(obj,
else (type(usage) == "string" and usage)
error("Type "..type(obj).." cannot be set as a help message") or "No description provided.")
end end
return self return self
end end
--print the help message, or generate it if there is none --print the help message, or generate it if there is none
function command:get_help() function command:get_help()
if not self.help then if not self.help then
self:generate_help("Description not defined") self:generate_help("Description not defined")
end end
return self.help return self.help
end end
function command:set_timeout_callback(fn) function command:set_timeout_callback(fn)
assert(type(fn) == "function","function expected, got "..type(fn)) assert(type(fn) == "function","function expected, got "..type(fn))
self.timeout_callback = fn self.timeout_callback = fn
return self return self
end end
--check the permissions for command --check the permissions for command
function command:check_permissions(message) function command:check_permissions(message)
if message.author.bot and (not self.options.allow_bots) then if message.author.bot and (not self.options.allow_bots) then
return false return false
end
if discordia.Date():toMilliseconds()-self.options.timeout < self.timer then
if self.timeout_callback then
self.timeout_callback(fn)
return false
end end
end if discordia.Date():toMilliseconds()-self.options.timeout < self.timer then
self.timer = discordia.Date():toMilliseconds() if self.timeout_callback then
if self.rules:check_user(message.author.id) then self.timeout_callback(message)
local found,allow = self.rules:check_user(message.author.id) return false
return allow end
end end
if self.rules:check_group(message.member.roles) then self.timer = discordia.Date():toMilliseconds()
local found,allow = self.rules:check_group(message.member.roles) if self.rules:check_user(tostring(message.author.id)) then
return allow local found,allow = self.rules:check_user(tostring(message.author.id))
end return allow
return self.rules:check_perm(message.member:getPermissions(message.channel)) 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 end
--the main entry point for the command - execute the callback within after --the main entry point for the command - execute the callback within after
--multiple checks --multiple checks
function command:exec(message,args,opts) function command:exec(message,args,opts)
local exec = self.callback local exec = self.callback
if not self.callback then if not self.callback then
error("Callback not set for command "..self.name) error("Callback not set for command "..self.name)
end end
if self.decorator then if self.decorator then
self.callback = self.decorator(self.callback) self.callback = self.decorator(self.callback)
end end
local content
if self.options.regex then
content = message.content
else
local strstart,strend = message.content:find(self.name,1,true) local strstart,strend = message.content:find(self.name,1,true)
content = message.content:sub(strend+1,-1) content = message.content:sub(strend+1,-1)
end if self:check_permissions(message) then
if self:check_permissions(message) then if self.options.typing_decorator then
if self.options.typing_decorator then message.channel:broadcastTyping()
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
end end
else local status,args,opts,err = import("air").parse(content,self.args,message.client,message.guild.id)
message:addReaction("⚠️") if status then
message:reply("An internal error occured: "..status) local callst,status,response = pcall(self.callback,message,args,opts)
end if callst then
else if type(status) == "boolean" then
message:addReaction("") message:addReaction((status and "") or "")
message:reply(err) end
return
end
message:addReaction("⚠️")
message:reply("An internal error occured: "..status)
return
end
message:addReaction("")
message:reply(err)
return
end end
else
message:addReaction("") message:addReaction("")
end
end end
--add decorators for the callback --add decorators for the callback
function command:set_decorator(fn) function command:set_decorator(fn)
assert(type(fn) == "function","a decorator function expected, got "..type(fn)) assert(type(fn) == "function","a decorator function expected, got "..type(fn))
self.decorator = fn self.decorator = fn
return self return self
end end
--get a list of all properties of the command --get a list of all properties of the command
function command:get_properties() function command:get_properties()
return { return {
name = self.name, name = self.name,
category = self.options.category, category = self.options.category,
args = table_utils.deepcopy(self.args), args = table_utils.deepcopy(self.args),
help = table_utils.deepcopy(self.help), help = table_utils.deepcopy(self.help),
prefix = self.prefix prefix = self.prefix
} }
end end
return command return command

View File

@ -2,94 +2,94 @@ local class = import("classes.baseclass")
local emitter_proxy = class("EmitterProxy") local emitter_proxy = class("EmitterProxy")
function emitter_proxy:__init(emitter) function emitter_proxy:__init(emitter)
self.original = emitter self.original = emitter
self.callback_pool = {} self.callback_pool = {}
end end
function emitter_proxy:on(event,callback) function emitter_proxy:on(event,callback)
if not self.callback_pool[event] then if not self.callback_pool[event] then
self.callback_pool[event] = {} self.callback_pool[event] = {}
end end
self.callback_pool[event][callback] = callback self.callback_pool[event][callback] = callback
self.original:on(event,callback) self.original:on(event,callback)
return callback return callback
end end
function emitter_proxy:once(event,callback) function emitter_proxy:once(event,callback)
if not self.callback_pool[event] then if not self.callback_pool[event] then
self.callback_pool[event] = {} self.callback_pool[event] = {}
end end
local wrapper = function(...) local wrapper = function(...)
callback(...) callback(...)
self.callback_pool[event][callback] = nil self.callback_pool[event][callback] = nil
end end
self.callback_pool[event][callback] = wrapper self.callback_pool[event][callback] = wrapper
self.callback_pool[event][wrapper] = wrapper self.callback_pool[event][wrapper] = wrapper
self.original:once(event,wrapper) self.original:once(event,wrapper)
return callback return callback
end end
function emitter_proxy:removeListener(event,callback) function emitter_proxy:removeListener(event,callback)
if self.callback_pool[event] and self.callback_pool[event][callback] then if self.callback_pool[event] and self.callback_pool[event][callback] then
self.callback_pool[event][callback] = nil self.callback_pool[event][callback] = nil
self.original:removeListener(event,callback) self.original:removeListener(event,callback)
end end
end end
function emitter_proxy:removeAllListeners(event,callback) function emitter_proxy:removeAllListeners(event,callback)
if self.callback_pool[event] then if self.callback_pool[event] then
for k,v in pairs(self.callback_pool[event]) do for k,v in pairs(self.callback_pool[event]) do
self.original:removeListener(event,v) self.original:removeListener(event,v)
end
self.callback_pool[event] = nil
end end
self.callback_pool[event] = nil
end
end end
function emitter_proxy:listeners(event) function emitter_proxy:listeners(event)
local copy = {} local copy = {}
if self.callback_pool[event] then if self.callback_pool[event] then
for k,v in pairs(self.callback_pool[event]) do for k,v in pairs(self.callback_pool[event]) do
table.insert(copy,v) table.insert(copy,v)
end
end end
end return copy
return copy
end end
function emitter_proxy:listenerCount(event) function emitter_proxy:listenerCount(event)
local count = 0 local count = 0
if event then if event then
if self.callback_pool[event] then if self.callback_pool[event] then
for k,v in pairs(self.callback_pool[event]) do for k,v in pairs(self.callback_pool[event]) do
count = count + 1 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 end
else return count
for k,v in pairs(self.callback_pool) do
for k2,v2 in pairs(v) do
count = count + 1
end
end
end
return count
end end
function emitter_proxy:propogate(event,emitter) function emitter_proxy:propogate(event,emitter)
if not self.callback_pool[event] then if not self.callback_pool[event] then
self.callback_pool[event] = {} self.callback_pool[event] = {}
end end
local emitter_propogate_handler = function(...) local emitter_propogate_handler = function(...)
emitter:emit(event,...) emitter:emit(event,...)
end end
self.callback_pool[event][emitter_propogate_handler] = emitter_propogate_handler self.callback_pool[event][emitter_propogate_handler] = emitter_propogate_handler
self.original:on(event,emitter_propogate_handler) self.original:on(event,emitter_propogate_handler)
return emitter_propogate_handler return emitter_propogate_handler
end end
function emitter_proxy:destroy() function emitter_proxy:destroy()
for k,v in pairs(self.callback_pool) do for k,v in pairs(self.callback_pool) do
for k2,v2 in pairs(v) do for k2,v2 in pairs(v) do
self.original:removeListener(k,v2) self.original:removeListener(k,v2)
end
end end
end
end end
return emitter_proxy return emitter_proxy

View File

@ -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

View File

@ -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)")

View File

@ -12,130 +12,130 @@ local core = import("core")
local emitter_proxy = import("classes.emitter-proxy") local emitter_proxy = import("classes.emitter-proxy")
local table_utils = import("table-utils") local table_utils = import("table-utils")
function plugin_handler:__init(parent_server) function plugin_handler:__init(parent_server)
assert(parent_server,"server handler to assign the plugin handler to has not been provided") assert(parent_server,"server handler to assign the plugin handler to has not been provided")
self.server_handler = parent_server self.server_handler = parent_server
self.plugins = {} self.plugins = {}
self.plugin_info = {} self.plugin_info = {}
self.plugin_paths = {} self.plugin_paths = {}
self.server_handler.event_emitter:on("serverSaveConfig",function() self.server_handler.event_emitter:on("serverSaveConfig",function()
print("[SERVER] Saving plugins configs") print("[SERVER] Saving plugins configs")
for name,plugin in pairs(self.plugins) do for name,plugin in pairs(self.plugins) do
self:save_plugin_config(name) self:save_plugin_config(name)
end end
end) end)
end end
function plugin_handler:load_plugin_config(name) 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 end
function plugin_handler:save_plugin_config(name) function plugin_handler:save_plugin_config(name)
if self.plugins[name] then if self.plugins[name] then
file.writeJSON(self.server_handler.config_path..name..".json",self.plugins[name].__env.config) file.writeJSON(self.server_handler.config_path..name..".json",self.plugins[name].__env.config)
end end
end end
function plugin_handler:add_plugin_folder(path) function plugin_handler:add_plugin_folder(path)
assert(type(path) == "string","path should be a string, got "..type(path)) assert(type(path) == "string","path should be a string, got "..type(path))
table.insert(self.plugin_paths,path) table.insert(self.plugin_paths,path)
end end
function plugin_handler:scan_folder(path) function plugin_handler:scan_folder(path)
local file = io.open(path.."/meta.json","r") local file = io.open(path.."/meta.json","r")
if file then if file then
local metadata,code,err = json.decode(file:read("*a")) local metadata,code,err = json.decode(file:read("*a"))
if metadata and metadata.name then if metadata and metadata.name then
self.plugin_info[metadata.name] = metadata self.plugin_info[metadata.name] = metadata
self.plugin_info[metadata.name].path = path.."/" self.plugin_info[metadata.name].path = path.."/"
self.plugin_info[metadata.name].loaded = false self.plugin_info[metadata.name].loaded = false
end 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
file:close() 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
end end
function plugin_handler:update_plugin_info() function plugin_handler:update_plugin_info()
for k,v in pairs(self.plugin_paths) do for k,v in pairs(self.plugin_paths) do
if file.existsDir(v) then if file.existsDir(v) then
file.ls(v):gsub("[^\n]+",function(c) file.ls(v):gsub("[^\n]+",function(c)
self:scan_folder(v..c) self:scan_folder(v..c)
end) end)
end
end end
end
end end
function plugin_handler:list_loadable() function plugin_handler:list_loadable()
return table_utils.deepcopy(self.plugin_info) return table_utils.deepcopy(self.plugin_info)
end end
function plugin_handler:load(name) function plugin_handler:load(name)
if not self.plugin_info[name] then if not self.plugin_info[name] then
return false, "No such plugin" return false, "No such plugin"
end end
if not self.plugin_info[name].main then 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" return false, "Plugin metadata entry doesn't specify the main file path or main file isn't found"
end end
if self.plugin_info[name].loaded then if self.plugin_info[name].loaded then
return false, "Plugin is already loaded" return false, "Plugin is already loaded"
end end
local environment = setmetatable({ local environment = setmetatable({
id = self.server_handler.id, id = self.server_handler.id,
globals = self.server_handler.config, globals = self.server_handler.config,
signals = emitter_proxy(self.server_handler.signal_emitter), signals = emitter_proxy(self.server_handler.signal_emitter),
client = self.server_handler.client, client = self.server_handler.client,
events = emitter_proxy(self.server_handler.event_emitter), events = emitter_proxy(self.server_handler.event_emitter),
discordia = import("discordia"), discordia = import("discordia"),
server = self.server_handler, server = self.server_handler,
command_handler = self.server_handler.command_handler, command_handler = self.server_handler.command_handler,
plugin_handler = self.server_handler.plugin_handler, plugin_handler = self.server_handler.plugin_handler,
log = function() end, log = function() end,
config = self:load_plugin_config(name), config = self:load_plugin_config(name),
import = import, import = import,
},{__index = _G}) },{__index = _G})
local plugin_meta = self.plugin_info[name] local plugin_meta = self.plugin_info[name]
if file.exists(plugin_meta.path..plugin_meta.main) then if file.exists(plugin_meta.path..plugin_meta.main) then
environment["plugin_path"] = plugin_meta.path environment["plugin_path"] = plugin_meta.path
local plugin_content = file.read(plugin_meta.path..plugin_meta.main,"*a") 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) local plugin_loader,err = load(plugin_content,"plugin loader: "..plugin_meta.path..plugin_meta.main,nil,environment)
if plugin_loader then if plugin_loader then
local plugin_object = plugin_loader() local plugin_object = plugin_loader()
if plugin_object then if plugin_object then
plugin_object.name = name plugin_object.name = name
plugin_object:load(environment) plugin_object:load(environment)
self.plugins[name] = plugin_object self.plugins[name] = plugin_object
self.plugins[name].__env = environment self.plugins[name].__env = environment
self.plugin_info[name].loaded = true self.plugin_info[name].loaded = true
return true return true
else else
return false, "Plugin object missing" return false, "Plugin object missing"
end end
else else
return false, err return false, err
end
else
return false, "File specified as the main file is inaccessible"
end end
else
return false, "File specified as the main file is inaccessible"
end
end end
function plugin_handler:unload(name) function plugin_handler:unload(name)
if self.plugins[name] then if self.plugins[name] then
self.plugins[name].__env.signals:destroy() self.plugins[name].__env.signals:destroy()
self.plugins[name].__env.events:destroy() self.plugins[name].__env.events:destroy()
self.plugins[name]:unload() self.plugins[name]:unload()
self.plugin_info[name].loaded = false self.plugin_info[name].loaded = false
return true return true
else else
return false,"Plugin is not loaded" return false,"Plugin is not loaded"
end end
end end
return plugin_handler return plugin_handler

View File

@ -2,58 +2,58 @@ local class = import("classes.baseclass")
local plugin = class("Plugin") local plugin = class("Plugin")
function plugin:__init() function plugin:__init()
self.command_pool = {} self.command_pool = {}
self.config = {} self.config = {}
end end
function plugin:load(environment) function plugin:load(environment)
self.command_handler = environment.server.command_handler self.command_handler = environment.server.command_handler
for k,v in pairs(self.command_pool) do for k,v in pairs(self.command_pool) do
self.command_handler:add_command(v) self.command_handler:add_command(v)
end end
end end
function plugin:unload() function plugin:unload()
if self.removal_callback then if self.removal_callback then
self.removal_callback() self.removal_callback()
end end
for k,v in pairs(self.command_pool) do for k,v in pairs(self.command_pool) do
self.command_handler:remove_command(v) self.command_handler:remove_command(v)
end end
end end
function plugin:for_all_commands(fn) function plugin:for_all_commands(fn)
assert(type(fn)=="function","function expected, got "..type(fn)) assert(type(fn)=="function","function expected, got "..type(fn))
for k,v in pairs(self.command_pool) do for k,v in pairs(self.command_pool) do
fn(v) fn(v)
end end
end end
function plugin:for_every_new_command(fn) function plugin:for_every_new_command(fn)
assert(type(fn)=="function","function expected, got "..type(fn)) assert(type(fn)=="function","function expected, got "..type(fn))
self.decorator = fn self.decorator = fn
end end
function plugin:add_command(command_object) function plugin:add_command(command_object)
if self.decorator then if self.decorator then
self.fn(command_object) self.fn(command_object)
end end
command_object.parent = self command_object.parent = self
self.command_pool[command_object] = command_object self.command_pool[command_object] = command_object
--in post init state: we request the command handler to add the commands --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 --that way, we can link our plugin back to the command handler
if self.command_handler then if self.command_handler then
self.command_handler:add_command(command_object) self.command_handler:add_command(command_object)
end end
end end
function plugin:remove_command(command_object) function plugin:remove_command(command_object)
if self.command_pool[command_object] then if self.command_pool[command_object] then
self.command_pool[command_object] = nil self.command_pool[command_object] = nil
end end
--remove command after post-init state --remove command after post-init state
if self.command_handler then if self.command_handler then
self.command_handler:remove_command(command_object) self.command_handler:remove_command(command_object)
end end
end end
return plugin return plugin

View File

@ -8,96 +8,96 @@ local eventlist = import("eventlist")
local discordia = import("discordia") local discordia = import("discordia")
local function check_partitioning(id,...) local function check_partitioning(id,...)
args = {...} args = {...}
v = args[1] v = args[1]
if type(v) == "table" and v.guild and v.guild.id == id then if type(v) == "table" and v.guild and v.guild.id == id then
return true return true
elseif not (type(v) == "table") then elseif not (type(v) == "table") then
return true return true
elseif type(v) == "table" and (not v.guild) and (tostring(v):find("Guild: ")) and v.id == id then elseif type(v) == "table" and (not v.guild) and (tostring(v):find("Guild: ")) and v.id == id then
return true return true
elseif type(v) == "table" and (not v.guild) and (v.message) and (v.message.guild.id == id) then elseif type(v) == "table" and (not v.guild) and (v.message) and (v.message.guild.id == id) then
return true return true
else else
return false return false
end end
end end
function server_handler:__init(client,guild,options) function server_handler:__init(client,guild,options)
assert(type(client) == "table","discordia client expected, got "..type(client)) assert(type(client) == "table","discordia client expected, got "..type(client))
self.client = client self.client = client
self.uptime = discordia.Date() self.uptime = discordia.Date()
self.event_emitter = core.Emitter:new() self.event_emitter = core.Emitter:new()
self.signal_emitter = core.Emitter:new() self.signal_emitter = core.Emitter:new()
self.plugin_handler = plugin_handler(self) self.plugin_handler = plugin_handler(self)
self.command_handler = command_handler(self) self.command_handler = command_handler(self)
self.id = guild.id self.id = guild.id
--conifgurable properties --conifgurable properties
self.config_path = options.path or "./servers/%id/" self.config_path = options.path or "./servers/%id/"
self.autosave = options.path or true self.autosave = options.path or true
self.autosave_frequency = options.autosave_frequency or 10 self.autosave_frequency = options.autosave_frequency or 10
self.plugin_search_paths = options.plugin_search_paths or {"./plugins/"} self.plugin_search_paths = options.plugin_search_paths or {"./plugins/"}
self.default_plugins = options.default_plugins or {"test"} self.default_plugins = options.default_plugins or {"test"}
self.default_prefixes = options.default_prefixes or {"&","<@!"..self.client.user.id..">"} self.default_prefixes = options.default_prefixes or {"&","<@!"..self.client.user.id..">"}
self.config = {} self.config = {}
self.config_path = self.config_path:gsub("%%id",self.id) self.config_path = self.config_path:gsub("%%id",self.id)
self:load_config() self:load_config()
self.config["prefix"] = self.config["prefix"] or self.default_prefixes[1] or "(missing prefix)" self.config["prefix"] = self.config["prefix"] or self.default_prefixes[1] or "(missing prefix)"
self.message_counter = 0 self.message_counter = 0
if self.autosave then if self.autosave then
self.client:on("messageCreate",function(msg) self.client:on("messageCreate",function(msg)
self.message_counter = self.message_counter + 1 self.message_counter = self.message_counter + 1
if math.fmod(self.message_counter,self.autosave_frequency) == 0 then if math.fmod(self.message_counter,self.autosave_frequency) == 0 then
self:save_config() self:save_config()
end end
end) end)
end end
if not file.existsDir(self.config_path) then if not file.existsDir(self.config_path) then
os.execute("mkdir -p "..self.config_path) os.execute("mkdir -p "..self.config_path)
end end
for k,v in pairs(eventlist) do for k,v in pairs(eventlist) do
self.client:on(v,function(...) self.client:on(v,function(...)
--check if the event is for this server, and then emit. --check if the event is for this server, and then emit.
if check_partitioning(self.id,...) then if check_partitioning(self.id,...) then
self.event_emitter:emit(v,...) self.event_emitter:emit(v,...)
end end
end) end)
end end
self.client:on("messageCreate",function(msg) self.client:on("messageCreate",function(msg)
if msg.guild and msg.guild.id == self.id then if msg.guild and msg.guild.id == self.id then
self.command_handler:handle(msg) 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
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 end
function server_handler:load_config(path) function server_handler:load_config(path)
print("[SERVER] Loading config") print("[SERVER] Loading config")
if path then if path then
self.config = file.readJSON(path,{}) self.config = file.readJSON(path,{})
else else
self.config = file.readJSON(self.config_path.."config.json") self.config = file.readJSON(self.config_path.."config.json")
end end
self.event_emitter:emit("serverLoadConfig",self.config) self.event_emitter:emit("serverLoadConfig",self.config)
end end
function server_handler:save_config(path) function server_handler:save_config(path)
print("[SERVER] Saving config") print("[SERVER] Saving config")
if path then if path then
file.writeJSON(path,self.config) file.writeJSON(path,self.config)
else else
file.writeJSON(self.config_path.."config.json",self.config) file.writeJSON(self.config_path.."config.json",self.config)
end end
self.event_emitter:emit("serverSaveConfig",self.config) self.event_emitter:emit("serverSaveConfig",self.config)
end end
return server_handler return server_handler

View File

@ -7,4 +7,29 @@ local save = command("save",{
end end
}) })
plugin:add_command(save) 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 return plugin