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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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