diff --git a/.gitignore b/.gitignore index 9fd3707..c79f01b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /token +/servers +/discordia.log +/gateway.json diff --git a/bot.lua b/bot.lua index 13948b0..287369f 100644 --- a/bot.lua +++ b/bot.lua @@ -12,13 +12,14 @@ local server = import("classes.server-handler") client:on("ready",function() print("starting test") local new_server = server(client,client:getGuild("640251445949759499"),{ - autosave_frequency = 20, + autosave_frequency = 5, default_plugins = { "meta", "help", "plugins", "esolang", - "tools" + "tools", + "reactions" } }) end) diff --git a/discordia.log b/discordia.log deleted file mode 100644 index 8579b21..0000000 --- a/discordia.log +++ /dev/null @@ -1,29 +0,0 @@ -2021-11-26 20:38:37 | [INFO] | Discordia 2.8.4 -2021-11-26 20:38:37 | [INFO] | Connecting to Discord... -2021-11-26 20:38:38 | [INFO] | Authenticated as 512mb.org#1582 -2021-11-26 20:38:38 | [INFO] | Launching shard 0 (1 out of 1)... -2021-11-26 20:38:39 | [INFO] | Shard 0 : Connected to wss://gateway.discord.gg -2021-11-26 20:38:39 | [INFO] | Shard 0 : Received HELLO -2021-11-26 20:38:39 | [INFO] | Shard 0 : Received READY -2021-11-26 20:42:01 | [INFO] | Discordia 2.8.4 -2021-11-26 20:42:01 | [INFO] | Connecting to Discord... -2021-11-26 20:42:01 | [INFO] | Authenticated as 512mb.org#1582 -2021-11-26 20:42:01 | [INFO] | Launching shard 0 (1 out of 1)... -2021-11-26 20:42:01 | [INFO] | Shard 0 : Connected to wss://gateway.discord.gg -2021-11-26 20:42:01 | [INFO] | Shard 0 : Received HELLO -2021-11-26 20:42:02 | [INFO] | Shard 0 : Received READY -2021-11-26 20:43:30 | [INFO] | Discordia 2.8.4 -2021-11-26 20:43:30 | [INFO] | Connecting to Discord... -2021-11-26 20:43:30 | [INFO] | Authenticated as 512mb.org#1582 -2021-11-26 20:43:30 | [INFO] | Launching shard 0 (1 out of 1)... -2021-11-26 20:43:31 | [INFO] | Shard 0 : Connected to wss://gateway.discord.gg -2021-11-26 20:43:31 | [INFO] | Shard 0 : Received HELLO -2021-11-26 20:43:31 | [INFO] | Shard 0 : Received READY -2021-11-26 20:49:51 | [INFO] | Discordia 2.8.4 -2021-11-26 20:49:51 | [INFO] | Connecting to Discord... -2021-11-26 20:49:51 | [INFO] | Authenticated as 512mb.org#1582 -2021-11-26 20:49:51 | [INFO] | Launching shard 0 (1 out of 1)... -2021-11-26 20:49:51 | [INFO] | Shard 0 : Connected to wss://gateway.discord.gg -2021-11-26 20:49:51 | [INFO] | Shard 0 : Received HELLO -2021-11-26 20:49:52 | [INFO] | Shard 0 : Received READY -2021-11-26 20:50:10 | [ERROR] | 400 - Bad Request : GET https://discord.com/api/v7/guilds/640251445949759499/members/nil diff --git a/gateway.json b/gateway.json deleted file mode 100644 index 8bfc425..0000000 --- a/gateway.json +++ /dev/null @@ -1 +0,0 @@ -{"url":"wss://gateway.discord.gg","913818433748086784":{"owner":{"discriminator":"7452","public_flags":0,"id":"245973168257368076","username":"Yessiest","avatar":"f8e9fa5b64a0333da12b9ece6d636db3","flags":0},"timestamp":1637944718,"shards":1}} \ No newline at end of file diff --git a/libraries/classes/command-handler.lua b/libraries/classes/command-handler.lua index f5ce14e..0bc2d9a 100644 --- a/libraries/classes/command-handler.lua +++ b/libraries/classes/command-handler.lua @@ -90,8 +90,6 @@ function command_handler:get_commands_metadata() return table_utils.deepcopy(self.command_meta) end function command_handler:handle(message) - print("msg: "..tostring(message.content)) - print("author: "..tostring(message.author.name)) for name,command in pairs(self.command_pool) do if command.options.regex then if message.content:match(command.options.regex) then @@ -101,13 +99,13 @@ function command_handler:handle(message) else if command.options.prefix then for _,prefix in pairs(self.prefixes) do - if message.content:find(prefix..name.."$") == 1 or message.content:find(prefix..name.."%s") then + if message.content:match("^"..prefix..name.."$") or message.content:match("^"..prefix..name.."%s") then command:exec(message) return end end else - if message.content:find(name.."$") == 1 or message.content:find(name.."%s") then + if message.content:match("^"..name.."$") or message.content:match("^"..name.."%s") then command:exec(message) return end diff --git a/libraries/classes/plugin-handler.lua b/libraries/classes/plugin-handler.lua index 92ae61a..2348e1d 100644 --- a/libraries/classes/plugin-handler.lua +++ b/libraries/classes/plugin-handler.lua @@ -10,13 +10,29 @@ local file = import("file") local json = import("json") local core = import("core") 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) 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",{}) +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 end function plugin_handler:add_plugin_folder(path) @@ -83,6 +99,7 @@ function plugin_handler:load(name) 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] @@ -108,7 +125,7 @@ function plugin_handler:load(name) else return false, "File specified as the main file is inaccessible" end -end +end function plugin_handler:unload(name) if self.plugins[name] then diff --git a/libraries/classes/plugin.lua b/libraries/classes/plugin.lua index 6ed4a92..625ff23 100644 --- a/libraries/classes/plugin.lua +++ b/libraries/classes/plugin.lua @@ -3,6 +3,7 @@ local plugin = class("Plugin") function plugin:__init() self.command_pool = {} + self.config = {} end function plugin:load(environment) diff --git a/libraries/classes/server-handler.lua b/libraries/classes/server-handler.lua index 96cb266..4f0d25d 100644 --- a/libraries/classes/server-handler.lua +++ b/libraries/classes/server-handler.lua @@ -33,19 +33,19 @@ function server_handler:__init(client,guild,options) self.plugin_handler = plugin_handler(self) self.command_handler = command_handler(self) self.id = guild.id - self.config = {} --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_path = self.config_path:gsub("%id",self.id) + 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 autosave then + 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 @@ -74,7 +74,7 @@ function server_handler:__init(client,guild,options) end self.plugin_handler:update_plugin_info() for _,plugin_name in pairs(self.default_plugins) do - print(self.plugin_handler:load(plugin_name)) + 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) @@ -82,19 +82,22 @@ function server_handler:__init(client,guild,options) 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) 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("serverSaveConfig") + self.event_emitter:emit("serverSaveConfig",self.config) end return server_handler diff --git a/libraries/markov.lua b/libraries/markov.lua index debe1fe..0b7e1af 100644 --- a/libraries/markov.lua +++ b/libraries/markov.lua @@ -18,12 +18,13 @@ end local function register_words(str,word_list) local word_list = word_list or {} - str:gsub("%S+",function(word) + str:gsub("%S+",function(word) if not word_list[word] then word_list[word] = {} end local current_word = word_list[word] - str:gsub(escape(word) .. "%s+(%S+)",function(word2) + local escaped_word = escape(word) + str:gsub("%s+" .. escaped_word .. "%s+(%S+)",function(word2) if not current_word[word2] then current_word[word2] = {} end @@ -33,7 +34,7 @@ local function register_words(str,word_list) current_word[word2].occurences = current_word[word2].occurences + 1 end end) - end) + end) for k,v in pairs(word_list) do word_list[k] = node(v) end diff --git a/plugins/debug/init.lua b/plugins/debug/init.lua index 4abc195..6408cf6 100644 --- a/plugins/debug/init.lua +++ b/plugins/debug/init.lua @@ -1,5 +1,10 @@ -local plugin_c = import("classes.plugin") +local plugin = import("classes.plugin")("debug") local command = import("classes.command") -local plugin = plugin_c() - +local save = command("save",{ + help = "Force-save config data", + exec = function() + server:save_config() + end +}) +plugin:add_command(save) return plugin diff --git a/plugins/meta/init.lua b/plugins/meta/init.lua index 3ad5239..ec9dcca 100644 --- a/plugins/meta/init.lua +++ b/plugins/meta/init.lua @@ -1,13 +1,12 @@ -segment = {} -segment.help = "This is a set of commands that add some features specific to this bot" - -local map = {} local aliases = {} local fake_message = import("fake_message") local last_message_arrived = discordia.Stopwatch() local unixToString = import("unixToString") local command = import("classes.command") local plugin = import("classes.plugin")("meta") +if not config.aliases then + config.aliases = {} +end client:on("messageCreate",function(msg) last_message_arrived:reset() @@ -22,15 +21,17 @@ for k,v in pairs(command_handler:get_prefixes()) do end local function add_alias(name,comm,prefix,description) - if (not map[name]) then - map[name] = {comm = comm,prefix = prefix} + if (not aliases[name]) then + print("[ALIAS] Adding alias \""..name.."\" for \""..comm.."\"") + config.aliases[name] = {comm = comm,prefix = prefix} aliases[name] = command(name,{ help = "Alias for ``"..comm.."``", usage = ((prefix and globals.prefix) or "")..name, exec = function(msg,args2,opts) - local str = msg.content:gsub(name.." ","") - aftersub = comm:gsub("%.%.%.",str) - aftersub = aftersub:gsub("%$prefix",prefix) + print("[ALIAS] Triggerting alias "..tostring(comm).." with args \""..tostring(msg.content).."\"") + local str = msg.content:gsub("^%S+ ?","") + aftersub = comm:gsub("%.%.%.",str or "") + aftersub = aftersub:gsub("%$prefix",prefix or "&") local status,args = require("air").parse(str) for k,v in pairs(args) do aftersub = aftersub:gsub("([^\\])%$"..k,"%1"..v) @@ -52,8 +53,8 @@ local function add_alias(name,comm,prefix,description) end local function remove_alias(name) - if map[name] then - map[name] = nil + if config.aliases[name] then + config.aliases[name] = nil plugin:remove_command(aliases[name]) return true else @@ -86,7 +87,7 @@ local function purify_strings(msg,input) return text end -for k,v in pairs(import("file").readJSON("./servers/"..id.."/aliasmap.json",{})) do +for k,v in pairs(config.aliases) do commdata = v if type(v) == "string" then --legacy format conversion commdata = {comm = v, prefix = false} @@ -180,7 +181,7 @@ local c_aliases = command("aliases", { title = "Aliases for this server", fields = (function() local fields = {} - for k,v in pairs(map) do + for k,v in pairs(config.aliases) do table.insert(fields,{name = ((v["prefix"] and prefix) or "")..k,value = v["comm"]}) end return fields @@ -331,18 +332,16 @@ local c_echo = command("echo",{ plugin:add_command(c_echo) plugin.removal_callback = function() - for k,v in pairs(map) do + for k,v in pairs(config.aliases) do remove_alias(k) end end local helpdb = import(plugin_path:sub(3,-1).."help") plugin:for_all_commands(function(command) - command:set_help(helpdb[command.name]) -end) - -events:on("serverSaveConfig",function() - import("file").writeJSON("./servers/"..id.."/aliasmap.json",map) + if helpdb[command.name] then + command:set_help(helpdb[command.name]) + end end) return plugin diff --git a/plugins/reactions/init.lua b/plugins/reactions/init.lua new file mode 100644 index 0000000..24c63b7 --- /dev/null +++ b/plugins/reactions/init.lua @@ -0,0 +1,334 @@ +local file = require("file") +local guild = client:getGuild(id) +local fake_message = require("fake_message") +local command = import("classes.command") +local plugin = import("classes.plugin")("reactions") +local segment = {} +segment.pivots = config + +local getEmoji = function(id) + local emoji = guild:getEmoji(id:match("(%d+)[^%d]*$")) + if emoji then + return emoji + else + return id + end +end + +local function count(tab) + local n = 0 + for k,v in pairs(tab) do + n = n + 1 + end + return n +end + +local pivot = command("pivot",{ + help = {embed={ + title = "Select a pivot message to manipulate", + description = "Pivot is like a message selector which allows easy reaction manipulations", + fields = { + {name = "Usage: ",value = "pivot "}, + {name = "Perms: ",valeu = "Administartor"} + } + }}, + args = { + "messageLink" + }, + perms = { + "administrator" + }, + exec = function(msg,args,opts) + if segment.pivot and count(segment.pivot.buttons) == 0 then + print("[REACTIONS] Deleting pivot: "..tostring(segment.pivot.message)) + segment.pivots[segment.pivot.message] = nil + end + local message = args[1] + if not message then + msg:reply("Couldn't find message with id "..args[2]) + return nil + end + if not segment.pivots[message.id] then + print("[REACTIONS] Creating pivot: "..tostring(segment.pivot.message)) + segment.pivots[message.id] = {} + segment.pivots[message.id].message = message.id + segment.pivots[message.id].channel = message.channel.id + segment.pivots[message.id].buttons = {} + end + segment.pivot = segment.pivots[message.id] + msg:reply("Pivot message set to "..message.link) + end + }) +plugin:add_command(pivot) + +local role_toggle = command("role-toggle",{ + help = {embed={ + title = "Add a simple role switch to the pivot", + description = "Note: you cannot assign more than one role to a single reaction", + fields = { + {name = "Usage: ",value = "role-toggle "}, + {name = "Perms: ",value = "administrator"} + } + }}, + args = { + "string", + "role", + }, + perms = { + "administrator" + }, + exec = function(msg,args,opts) + if not segment.pivot then + msg:reply("Pivot not selected. Use "..globals.prefix.."pivot to select it and then try again") + return nil + end + local emoji = getEmoji(args[1]) + local channel = guild:getChannel(segment.pivot.channel) + if not channel then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + local message = channel:getMessage(segment.pivot.message) + if not message then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + print("[REACTIONS] Adding role-toggle listener") + local grabEmoji = function(reaction) + segment.pivot.buttons[tostring(reaction.emojiId or reaction.emojiName)] = { + type = "role-toggler", + role = tostring(args[2].id) + } + msg:reply("Role toggler added successfully") + end + message:removeReaction(emoji,client.user.id) + client:once("reactionAdd",grabEmoji) + if not message:addReaction(emoji) then + client:removeListener("reactionAdd",grabEmoji) + msg:reply("Couldn't add reaction - emoji might be invalid") + end + end + }) +plugin:add_command(role_toggle) +local remove_reaction = command("remove-reaction",{ + help = {embed={ + title = "Remove a reaction from a pivot", + description = "If you don't specify a reaction to remove, the entire pivot for the message is removed automatically", + fields = { + {name = "Usage: ",value = "remove-reaction "}, + {name = "Perms: ",value = "Administrator"} + } + }}, + perms = { + "administrator" + }, + exec = function(msg,args,opts) + local channel = guild:getChannel(segment.pivot.channel) + if not channel then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + local message = channel:getMessage(segment.pivot.message) + if not message then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + print("[REACTIONS] Removing reaction listener") + if args[1] then + local emoji = getEmoji(args[1]) + message:removeReaction(emoji,client.user.id) + segment.pivot.buttons[((type(emoji) == "table") and emoji.id) or emoji] = nil + msg:reply("Action successfully removed") + else + message:clearReactions() + segment.pivots[tostring(message.id)] = nil + segment.pivot = nil + msg:reply("Pivot successfully removed") + end + end + }) +plugin:add_command(remove_reaction) +local toggle = command("toggle",{ + help = {embed={ + title = "Add a toggle that runs specific commands", + description = "Note: you cannot assign more than one action to a single reaction \n``$user`` gets replaced with the id of the user that interacted with the reaction.", + fields = { + {name = "Usage: ",value = "toggle "}, + {name = "Perms: ",value = "administrator"} + } + }}, + args = { + "string", + "string", + "string", + }, + perms = { + "administrator" + }, + exec = function(msg,args,opts) + if not segment.pivot then + msg:reply("Pivot not selected. Use "..globals.prefix.."pivot to select it and then try again") + return nil + end + local emoji = getEmoji(args[1]) + local channel = guild:getChannel(segment.pivot.channel) + if not channel then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + local message = channel:getMessage(segment.pivot.message) + if not message then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + print("[REACTIONS] Adding toggle listener") + local grabEmoji = function(reaction) + segment.pivot.buttons[tostring(reaction.emojiId or reaction.emojiName)] = { + type = "toggler", + on = args[2], + off = args[3], + } + msg:reply("Toggler added successfully") + end + message:removeReaction(emoji,client.user.id) + client:once("reactionAdd",grabEmoji) + if not message:addReaction(emoji) then + client:removeListener("reactionAdd",grabEmoji) + msg:reply("Couldn't add reaction - emoji might be invalid") + end + end + }) +plugin:add_command(toggle) +local button = command("button",{ + help = {embed={ + title = "Add a button that runs specific command when pressed", + description = "Note: you cannot assign more than one action to a single reaction \n``$user`` gets replaced with the id of the user that interacted with the reaction.", + fields = { + {name = "Usage: ",value = "button "}, + {name = "Perms: ",value = "administrator"} + } + }}, + args = { + "string", + "string", + }, + perms = { + "administrator" + }, + exec = function(msg,args,opts) + if not segment.pivot then + msg:reply("Pivot not selected. Use "..globals.prefix.."pivot to select it and then try again") + return nil + end + local emoji = getEmoji(args[1]) + local channel = guild:getChannel(segment.pivot.channel) + if not channel then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + local message = channel:getMessage(segment.pivot.message) + if not message then + msg:reply("Something went horribly wrong, but it's not your fault. This incident has been (hopefully) reported") + return nil + end + print("[REACTIONS] Adding button listener") + local grabEmoji = function(reaction) + segment.pivot.buttons[tostring(reaction.emojiId or reaction.emojiName)] = { + type = "button", + on = args[2], + } + msg:reply("Button added successfully") + end + message:removeReaction(emoji,client.user.id) + client:once("reactionAdd",grabEmoji) + if not message:addReaction(emoji) then + client:removeListener("reactionAdd",grabEmoji) + msg:reply("Couldn't add reaction - emoji might be invalid") + end + end + }) +plugin:add_command(button) + +local buttonOn = function(message,hash,userID) + if not message then + log("ERROR","Attempted to find a deleted message") + return + end + if segment.pivots[tostring(message.id)] and userID ~= client.user.id then + local current_pivot = segment.pivots[tostring(message.id)] + if current_pivot.buttons[tostring(hash)] then + local current_button = current_pivot.buttons[tostring(hash)] + local new_content + if current_button.on then + new_content = current_button.on:gsub("%$user",userID) + end + if current_button.type == "role-toggler" then + guild:getMember(userID):addRole(current_button.role) + end + if current_button.type == "toggler" then + command_handler:handle(fake_message(message,{ + delete = function() end, + content = new_content + })) + end + if current_button.type == "button" then + command_handler:handle(fake_message(message,{ + delete = function() end, + content = new_content + })) + end + end + end +end + +local buttonOff = function(message,hash,userID) + if not message then + log("ERROR","Attempted to find a deleted message") + return + end + if segment.pivots[tostring(message.id)] and userID ~= client.user.id then + local current_pivot = segment.pivots[tostring(message.id)] + if current_pivot.buttons[tostring(hash)] then + local current_button = current_pivot.buttons[tostring(hash)] + local new_content + if current_button.off then + new_content = current_button.off:gsub("%$user",userID) + end + if current_button.type == "role-toggler" then + guild:getMember(userID):removeRole(current_button.role) + end + if current_button.type == "toggler" then + command_handler:handle(fake_message(message,{ + delete = function() end, + content = new_content + })) + end + end + end +end + +events:on("reactionAdd",function(reaction,userID) + local message = reaction.message + local hash = tostring(reaction.emojiId or reaction.emojiName) + buttonOn(message,hash,userID) +end) + +events:on("reactionRemove",function(reaction,userID) + local message = reaction.message + local hash = tostring(reaction.emojiId or reaction.emojiName) + buttonOff(message,hash,userID) +end) + +events:on("reactionAddUncached",function(channelId,messageId,hash,userId) + local message = client:getChannel(channelId):getMessage(messageId) + local hash = tostring(hash) + buttonOn(message,hash,userId) +end) + +events:on("reactionRemoveUncached",function(channelId,messageId,hash,userId) + local message = client:getChannel(channelId):getMessage(messageId) + local hash = tostring(hash) + buttonOff(message,hash,userId) +end) + +return plugin