diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..631f40c --- /dev/null +++ b/.env.template @@ -0,0 +1,36 @@ +# Security Warning! Do not commit this file to any VCS! +# This is a local file to speed up development process, +# so you don't have to change your environment variables. +# +# This is not applied to `.env.template`! +# Template files must be committed to the VCS, but must not contain +# any secret values. + +BOT_TOKEN= +BOT_PREFIX=v! +BOT_VER=3.0 +BOT_AUTHOR=Vylpes +BOT_DATE=28 Nov 2021 +BOT_OWNERID=147392775707426816 + +CORE_VER=2.0.2 + +FOLDERS_COMMANDS=src/commands +FOLDERS_EVENTS=src/events + +COMMANDS_DISABLED= +COMMANDS_DISABLED_MESSAGE=This command is disabled. + +COMMANDS_ROLE_ROLES=Notify,VotePings,ProjectUpdates + +COMMANDS_RULES_FILE=data/rules/rules.json + +EMBED_COLOUR=0x3050ba +EMBED_COLOUR_ERROR=0xD52803 + +ROLES_MODERATOR=Moderator +ROLES_MUTED=Muted + +CHANNELS_LOGS_MESSAGE=message-logs +CHANNELS_LOGS_MEMBER=member-logs +CHANNELS_LOGS_MOD=mod-logs \ No newline at end of file diff --git a/commands/about.js b/commands/about.js deleted file mode 100644 index 5c2fbb2..0000000 --- a/commands/about.js +++ /dev/null @@ -1,45 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class about extends command { - constructor() { - // Set execute method, description, and category - super("about"); - super.description = "About the bot"; - super.category = "General"; - - // Set required configs in the config.about json string. - // description: The bot description - // version: The bot version - // author: Bot author - // date: Date of build - super.requiredConfigs = "description"; - super.requiredConfigs = "version"; - super.requiredConfigs = "core-ver"; - super.requiredConfigs = "author"; - super.requiredConfigs = "date"; - } - - // The execution method - about(context) { - // Create an embed containing data about the bot - const embed = new MessageEmbed() - .setTitle("About") - .setColor(embedColor) - .setDescription(context.client.config.about.description) - .addField("Version", context.client.config.about.version, true) - .addField("VylBot Core", context.client.config.about['core-ver'], true) - .addField("Author", context.client.config.about.author) - .addField("Date", context.client.config.about.date); - - // Send embed to the channel the command was sent in - context.message.channel.send(embed); - } -} - -// Set the about class to be exported -module.exports = about; diff --git a/commands/ban.js b/commands/ban.js deleted file mode 100644 index c8acbb9..0000000 --- a/commands/ban.js +++ /dev/null @@ -1,93 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class ban extends command { - constructor() { - // Set execution method, description, category, and usage - super("ban"); - super.description = "Bans the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set required configs in the config.ban json string - super.requiredConfigs = "modrole"; - super.requiredCofigs = "logchannel"; - } - - // Command execution method - ban(context) { - // If the user has the modrole (set in config.ban.modrole) - if (context.message.guild.roles.cache.find(role => role.name == context.client.config.ban.modrole)) { - // Gets the user pinged in the command - const user = context.message.mentions.users.first(); - - // If the user pinged is a valid user - if (user) { - // Get the guild member object from the pinged user - const member = context.message.guild.member(user); - - // If the member object exists, i.e. if they are in the server - if (member) { - // Get the arguments and remove what isn't the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the array into a string - const reason = reasonArgs.join(" "); - - // If the guild is available to work with - if (context.message.guild.available) { - // If the bot client is able to ban the member - if (member.bannable) { - // The Message Embed which goes into the bot log - const embedLog = new MessageEmbed() - .setTitle("Member Banned") - .setColor(embedColor) - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The Message Embed which goes into the public channel the message was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been banned`); - - // Ban the member and send the embeds into the appropriate channel, then delete the initial message - member.ban({ reason: reason }).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.ban.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { // If the bot couldn't ban the member, say so and log the error to the console - errorEmbed(context, "An error occurred"); - console.log(err); - }); - } - } - } else { // If the member object doesn't exist - errorEmbed(context, "User is not in this server"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "User does not exist"); - } - } else { // If the user doesn't have the mod role - errorEmbed(context, `You require the \`${context.client.config.ban.modrole}\` role to run this command`); - } - } -} - -// Post an error embed -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = ban; diff --git a/commands/bunny.js b/commands/bunny.js deleted file mode 100644 index 97baa86..0000000 --- a/commands/bunny.js +++ /dev/null @@ -1,35 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const randomBunny = require('random-bunny'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class bunny extends command { - constructor() { - // Set run method, description, and category - super("bunny"); - super.description = "Gives you a random bunny"; - super.category = "Fun"; - } - - // Run method - bunny(context) { - // Get a random post from r/Rabbits - randomBunny('rabbits', 'hot', (image, title) => { - // Create an embed containing the random image - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle(title) - .setImage(image) - .setFooter('r/Rabbits'); - - // Send the embed - context.message.channel.send(embed); - }); - } -} - -module.exports = bunny; diff --git a/commands/clear.js b/commands/clear.js deleted file mode 100644 index d84a288..0000000 --- a/commands/clear.js +++ /dev/null @@ -1,58 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class clear extends command { - constructor() { - // Set execute method, description, category, and usage - super("clear"); - super.description = "Bulk deletes the chat for up to 100 messages"; - super.category = "Moderation"; - super.usage = ""; - - // Set required configs in the config.clear json string - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - } - - // Execute method - clear(context) { - // If the user has the config.clear.modrole role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.clear.modrole)) { - // If the command specifies a number between 1 and 100 - if (context.arguments.length > 0 && context.arguments[0] > 0 && context.arguments[0] < 101) { - // Attempt to bulk delete the amount of messages specified as an argument - context.message.channel.bulkDelete(context.arguments[0]).then(() => { - // Public embed - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${context.arguments[0]} messages were removed`); - - // Send the embed into the channel the command was sent in - context.message.channel.send(embed); - }).catch(err => { // If the bot couldn't bulk delete - errorEmbed(context, "An error has occurred"); - console.log(err); - }); - } else { // If the user didn't give a number valid (between 1 and 100) - errorEmbed(context, "Please specify an amount between 1 and 100"); - } - } else { // If the user doesn't have the mod role - errorEmbed(context, `This command requires the \`${context.client.config.clear.modrole}\` role to run`); - } - } -} - -// Function to send an error embed -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = clear; diff --git a/commands/eval.js b/commands/eval.js deleted file mode 100644 index bdd46e1..0000000 --- a/commands/eval.js +++ /dev/null @@ -1,25 +0,0 @@ -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -class evaluate extends command { - constructor() { - super("evaluate"); - super.description = "Evaluates an expression"; - super.category = "Administration"; - super.requiredConfigs = "ownerid"; - } - - evaluate(context) { - if (context.message.author.id == context.client.config.eval.ownerid) { - const result = eval(context.arguments.join(" ")); - - const embed = new MessageEmbed() - .setDescription(result) - .setColor(0x3050ba); - - context.message.channel.send(embed); - } - } -} - -module.exports = evaluate; diff --git a/commands/help.js b/commands/help.js deleted file mode 100644 index 71d7946..0000000 --- a/commands/help.js +++ /dev/null @@ -1,150 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const { readdirSync } = require('fs'); - -const embedColor = "0x3050ba"; - -// Command Class -class help extends command { - constructor() { - // Set the execute method, description, category, and example usage - super("help"); - super.description = "Gives a list of commands available in the bot"; - super.category = "General"; - super.usage = "[command]"; - } - - // Execute method - help(context) { - // Get the list of command folders the bot has been setup to check - const commandFolders = context.client.config.commands; - - // Empty arrays for commands - // allCommands: Will contain objects of all commands with their related info - // categories: Will contain strings of all the categories the commands are set to, unique - const allCommands = []; - const categories = []; - - // Loop through all the command folders set - // i = folder index - for (let i = 0; i < commandFolders.length; i++) { - // The current folder the bot is looking through - const folder = commandFolders[i]; - - // Read the directory of the current folder - const contents = readdirSync(`${process.cwd()}/${folder}`); - - // Loop through the contents of the folder - // j = file index in folder i - for (let j = 0; j < contents.length; j++) { - // Get command in the current folder to read - const file = require(`${process.cwd()}/${folder}/${contents[j]}`); - - // Initialise the command - const obj = new file(); - - // Data object containing the command information - const data = { - "name": contents[j].replace(".js", ""), - "description": obj.description, - "category": obj.category, - "usage": obj.usage, - "roles": obj.roles - }; - - // Push the command data to the allCommands Array - allCommands.push(data); - } - } - - // Loop through all the commands discovered by the previous loop - for (let i = 0; i < allCommands.length; i++) { - // Get the current command category name, otherwise "none" - const category = allCommands[i].category || "none"; - - // If the command isn't already set, set it. - // This will then make the categories array be an array of all categories which have been used but only one of each. - if (!categories.includes(category)) categories.push(category); - } - - // If an command name has been passed as an argument - // If so, send information about that command - // If not, send the help embed of all commands - if (context.arguments[0]) { - sendCommand(context, allCommands, context.arguments[0]); - } else { - sendAll(context, categories, allCommands); - } - } -} - -// Send embed of all commands -// context: The command context json string -// categories: The array of categories found -// allCommands: The array of the commands found -function sendAll(context, categories, allCommands) { - // Embed to be sent - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle("Commands"); - - // Loop through each command - for (let i = 0; i < categories.length; i++) { - // The category name of the current one to check - const category = categories[i]; - - // Empty Array for the next loop to filter out the current category - const commandsFilter = []; - - // Loop through allCommands - // If the command is set to the current category being checked, add it to the filter array - for (let j = 0; j < allCommands.length; j++) { - if (allCommands[j].category == category) commandsFilter.push(`\`${allCommands[j].name}\``); - } - - // Add a field to the embed which contains the category name and all the commands in that category - embed.addField(category, commandsFilter.join(", ")); - } - - // Send the embed - context.message.channel.send(embed); -} - -// Send information about a specific command -// context: The command context json string -// allCommands: The array of categories found -// name: The command name to check -function sendCommand(context, allCommands, name) { - let command = {}; - - // Loop through all commands, if the command name is the same as the one we're looking for, select it - for (let i = 0; i < allCommands.length; i++) { - if (allCommands[i].name == name) command = allCommands[i]; - } - - // If a matching command has been found - if (command.name) { - // Create an embed containing the related information of the command - // The title is the command name but sets the first letter to be capitalised - // If a set of information isn't set, set it to say "none" - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle(command.name[0].toUpperCase() + command.name.substring(1)) - .setDescription(command.description || "*none*") - .addField("Category", command.category || "*none*", true) - .addField("Usage", command.usage || "*none*", true) - .addField("Required Roles", command.roles.join(", ") || "*none*"); - - // Send the embed - context.message.channel.send(embed); - } else { // If no command has been found, then send an embed which says this - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription("Command does not exist"); - - context.message.channel.send(embed); - } -} - -module.exports = help; diff --git a/commands/kick.js b/commands/kick.js deleted file mode 100644 index 4878270..0000000 --- a/commands/kick.js +++ /dev/null @@ -1,93 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class kick extends command { - constructor() { - // Sets the command's run method, description, category, and usage - super("kick"); - super.description = "Kicks the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Sets the required configs for the command - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - } - - // The command's run method - kick(context) { - // Checks if the user has the mod role, set in the config json string - if (context.message.member.roles.cache.find(role => role.name == context.client.config.kick.modrole)) { - // Gets the first user pinged in the command - const user = context.message.mentions.users.first(); - - // If a user was pinged - if (user) { - // Gets the guild member object of the pinged user - const member = context.message.guild.member(user); - - // If the member object exists, i.e if the user is in the server - if (member) { - // Gets the part of the argument array which holds the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Joins the reason into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // If the bot client can kick the mentioned member - if (member.kickable) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setTitle("Member Kicked") - .setColor(embedColor) - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been kicked`); - - // Attemtp to kick the user, if successful send the embeds, if unsuccessful notify the chat and log the error - member.kick({ reason: reason }).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.kick.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { - errorEmbed(context, "An error has occurred"); - console.log(err); - }); - } else { // If the user isn't kickable - errorEmbed(context, "I am unable to kick this user"); - } - } - } else { // If the member object is invalid - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object is invalid - errorEmbed(context, "Please specify a valid user"); - } - } - } -} - -// Function to post an embed in case of an error -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = kick; diff --git a/commands/mute.js b/commands/mute.js deleted file mode 100644 index 3b1fcd1..0000000 --- a/commands/mute.js +++ /dev/null @@ -1,98 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class mute extends command { - constructor() { - // Set the command's run method, description, category, and usage - super("mute"); - super.description = "Mutes the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set the required configs for the command - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - super.requiredConfigs = "muterole"; - } - - // The command's run method - mute(context) { - // Check if the user has the mod role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.mute.modrole)) { - // Get the user first pinged in the message - const user = context.message.mentions.users.first(); - - // If the user object exists - if (user) { - // Get the guild member object of the mentioned user - const member = context.message.guild.member(user); - - // If the member object exists, i.e. if the user is in the server - if (member) { - // Get the part of the arguments array which contains the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the reason into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // If the bot client can manage the user's roles - if (member.manageable) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setTitle("Member Muted") - .setColor(embedColor) - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into the channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been muted`) - .addField("Reason", reason || "*none*"); - - // Get the 'Muted' role - const mutedRole = context.message.guild.roles.cache.find(role => role.name == context.client.config.mute.muterole); - - // Attempt to mute the user, if successful send the embeds, if not log the error - member.roles.add(mutedRole, reason).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.mute.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { - errorEmbed(context, "An error occurred"); - console.log(err); - }); - } else { // If the bot can't manage the user - errorEmbed(context, "I am unable to mute this user"); - } - } - } else { // If the member object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } - } -} - -// Send an embed when an error occurs -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = mute; diff --git a/commands/partner.js b/commands/partner.js deleted file mode 100644 index bf59622..0000000 --- a/commands/partner.js +++ /dev/null @@ -1,60 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const { existsSync, readFileSync } = require('fs'); - -// Command Variables -const embedColor = "0x3050ba"; - -// Command class -class partner extends command { - constructor() { - // Set the command's run method, description, and category - super("partner"); - super.description = "Generates the embeds for the partner from the partners.json file"; - super.category = "Admin"; - - // Require in the config the name of the admin role and the rules file name - super.requiredConfigs = "adminrole"; - super.requiredConfigs = "partnersfile"; - } - - // Run method - partner(context) { - if (context.message.member.roles.cache.find(role => role.name == context.client.config.partner.adminrole)) { - if (existsSync(context.client.config.partner.partnersfile)) { - const partnerJson = JSON.parse(readFileSync(context.client.config.partner.partnersfile)); - - for (const i in partnerJson) { - const serverName = partnerJson[i].name; - const serverInvite = partnerJson[i].invite; - const serverDescription = partnerJson[i].description; - const serverIcon = partnerJson[i].icon; - - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle(serverName) - .setDescription(serverDescription) - .setURL(serverInvite) - .setThumbnail(serverIcon); - - context.message.channel.send(embed); - } - } else { - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription('File does not exist'); - - context.message.channel.send(errorEmbed); - } - } else { - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription('You do not have permission to run this command'); - - context.message.channel.send(errorEmbed); - } - } -} - -module.exports = partner; \ No newline at end of file diff --git a/commands/poll.js b/commands/poll.js deleted file mode 100644 index 7afe92e..0000000 --- a/commands/poll.js +++ /dev/null @@ -1,150 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const emojiRegex = require('emoji-regex/RGI_Emoji'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class poll extends command { - constructor() { - // Set the command's run method, description, category, and example usage - super("poll"); - super.description = "Generates a poll with reaction numbers"; - super.category = "General"; - super.usage = ";<option 1>;<option 2>..."; - } - - // Run method - poll(context) { - // Get the command's arguments, and split them by a semicolon rather than a space - // This allows the variables to be able to use spaces in them - let args = context.arguments; - const argsJoined = args.join(' '); - args = argsJoined.split(';'); - - // If the argument has 3 or more arguments and less than 11 arguments - // This allows the title and 2-9 options - if (args.length >= 3 && args.length < 11) { - // Set the title to the first argument - const title = args[0]; - let optionString = ""; - - // Array used to get the numbers as their words - // arrayOfNumbers[n] = "n written in full words" - const arrayOfNumbers = [ - ':zero:', - ':one:', - ':two:', - ':three:', - ':four:', - ':five:', - ':six:', - ':seven:', - ':eight:', - ':nine:' - ]; - - // Array containing the numbers as their emoji - const reactionEmojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]; - - // Loop through all the arguments after the title - // Add them to the optionString, with their index turned into a number emoji - // Example: :one: Option 1 - for (let i = 1; i < args.length; i++) { - // If the option contains an emoji, replace the emoji with it - const regex = emojiRegex(); - const match = regex.exec(args[i]); - - if (match) { - const emoji = match[0]; - reactionEmojis[i] = emoji; - arrayOfNumbers[i] = ''; - } - - optionString += `${arrayOfNumbers[i]} ${args[i]}\n`; - } - - // Create the embed with the title at the top of the description with the options below - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`**${title}**\n\n${optionString}`); - - // Send the embed and then react with the numbers for users to react with, - // the bot will determine how many to react with for the amount of options inputted - context.message.channel.send(embed).then(message => { - if (args.length == 2) { - message.react(reactionEmojis[1]); - } else if (args.length == 3) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])); - } else if (args.length == 4) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])); - } else if (args.length == 5) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])); - } else if (args.length == 6) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])); - } else if (args.length == 7) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])); - } else if (args.length == 8) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])) - .then(() => message.react(reactionEmojis[7])); - } else if (args.length == 9) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])) - .then(() => message.react(reactionEmojis[7])) - .then(() => message.react(reactionEmojis[8])); - } else if (args.length == 10) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])) - .then(() => message.react(reactionEmojis[7])) - .then(() => message.react(reactionEmojis[8])) - .then(() => message.react(reactionEmojis[9])); - } - }).catch(console.error); - - // Delete the message - context.message.delete(); - } else if (args.length >= 11) { // If the user inputted more than 9 options - const errorEmbed = new MessageEmbed() - .setDescription("The poll command can only accept up to 9 options"); - - context.message.channel.send(errorEmbed); - } else { // If the user didn't give enough data - const errorEmbed = new MessageEmbed() - .setDescription("Please use the correct usage: <title>;<option 1>;<option 2>... (separate options with semicolons)"); - - context.message.channel.send(errorEmbed); - } - } -} - -module.exports = poll; diff --git a/commands/role.js b/commands/role.js deleted file mode 100644 index 733be83..0000000 --- a/commands/role.js +++ /dev/null @@ -1,105 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class role extends command { - constructor() { - // Set the command's run method, description, category, and example usage - super("role"); - super.description = "Toggles a role for the user to gain/remove"; - super.category = "General"; - super.usage = "[name]"; - - // Require in the config the 'assignable roles' array - super.requiredConfigs = "assignable"; - } - - // Run method - role(context) { - // Get the array containing the assignable roles - const roles = context.client.config.role.assignable; - let requestedRole = ""; - - // If the arguments specifys a specific role - if (context.arguments.length > 0) { - // Loop through all the assignable roles and check against the first parameter - // Save the role name if they match, i.e. the role can be assignable - for (let i = 0; i < roles.length; i++) { - if (roles[i].toLowerCase() == context.arguments[0].toLowerCase()) { - requestedRole = roles[i]; - } - } - - // If a matching assignable role was found - if (requestedRole != "") { - // Get the role object from the server with the role name - const role = context.message.guild.roles.cache.find(r => r.name == requestedRole); - - // If the user already has the role, remove the role from them and send an embed - // Otherwise, add the role and send an embed - if (context.message.member.roles.cache.find(r => r.name == requestedRole)) { - context.message.member.roles.remove(role).then(() => { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`Removed role: ${requestedRole}`); - - context.message.channel.send(embed); - }).catch(err => { - console.error(err); - - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription("An error occured. Please check logs"); - - context.message.channel.send(errorEmbed); - }); - } else { // If the user doesn't have the role - context.message.member.roles.add(role).then(() => { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`Gave role: ${requestedRole}`); - - context.message.channel.send(embed); - }).catch(err => { - console.error(err); - - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription("An error occured. Please check logs"); - - context.message.channel.send(errorEmbed); - }); - } - } else { // If the role can't be found, send an error embed - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription("This role does not exist, see assignable roles with the role command (no arguments)"); - - context.message.channel.send(embed); - } - } else { // If no role was specified, Send a list of the roles you can assign - // The start of the embed text - let rolesString = `Do ${context.client.config.prefix}role <role> to get the role!\n`; - - // Loop through all the roles, and add them to the embed text - for (let i = 0; i < roles.length; i++) { - rolesString += `${roles[i]}\n`; - } - - // Create an embed containing the text - const embed = new MessageEmbed() - .setTitle("Roles") - .setColor(embedColor) - .setDescription(rolesString); - - // Send the embed - context.message.channel.send(embed); - } - } -} - -module.exports = role; diff --git a/commands/rules.js b/commands/rules.js deleted file mode 100644 index 266e504..0000000 --- a/commands/rules.js +++ /dev/null @@ -1,70 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const { existsSync, readFileSync } = require('fs'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class rules extends command { - constructor() { - // Set the command's run method, description, and category - super("rules"); - super.description = "Generates the rules embeds from the rules.txt file"; - super.category = "Admin"; - - // Require in the config the name of the admin role and the rules file name - super.requiredConfigs = "adminrole"; - super.requiredConfigs = "rulesfile"; - } - - // Run method - rules(context) { - // If the user is an Admin (has the admin role) - if (context.message.member.roles.cache.find(role => role.name == context.client.config.rules.adminrole)) { - // If the rulesfile exists - if (existsSync(context.client.config.rules.rulesfile)) { - const rulesJson = readFileSync(context.client.config.rules.rulesfile); - const rules = JSON.parse(rulesJson); - - for (let i = 0; i < rules.length; i++) { - const rule = rules[i]; - const embed = new MessageEmbed(); - - embed.setColor(embedColor); - - if (rule.image) embed.setImage(rule.image); - if (rule.title) embed.setTitle(rule.title); - if (rule.footer) embed.setFooter(rule.footer); - if (rule.description) { - let description = ""; - - for (let j = 0; j < rule.description.length; j++) { - const line = rule.description[j]; - description += `${line}\n`; - } - - embed.setDescription(description); - } - - context.message.channel.send(embed); - } - } else { // If the rules file doesn't exist - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${context.client.config.rules.rulesfile} doesn't exist`); - - context.message.channel.send(errorEmbed); - } - } else { // If the user doesn't have the Admin role - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription("You do not have permission to run this command"); - - context.message.channel.send(errorEmbed); - } - } -} - -module.exports = rules; diff --git a/commands/unmute.js b/commands/unmute.js deleted file mode 100644 index 24b7528..0000000 --- a/commands/unmute.js +++ /dev/null @@ -1,98 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class unmute extends command { - constructor() { - // Set run method, description, category, usage - super("unmute"); - super.description = "Unmutes the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set required configs - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - super.requiredConfigs = "muterole"; - } - - // The command's run method - unmute(context) { - // Check if the user has the mod role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.mute.modrole)) { - // Get the user first pinged in the message - const user = context.message.mentions.users.first(); - - // If the user object exists - if (user) { - // Get the guild member object from the pinged user - const member = context.message.guild.member(user); - - // If the member object exists, i.e. if the user is in the server - if (member) { - // Get the part of the argument array which contains the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the array into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // If the bot client can manage the user - if (member.manageable) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setColor(embedColor) - .setTitle("Member Unmuted") - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into the channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been unmuted`) - .addField("Reason", reason || "*none*"); - - // Get the muted role - const mutedRole = context.message.guild.roles.cache.find(role => role.name == context.client.config.unmute.muterole); - - // Attempt to remove the role from the user, and then send the embeds. If unsuccessful log the error - member.roles.remove(mutedRole, reason).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.unmute.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { - errorEmbed(context, "An error occurred"); - console.log(err); - }); - } else { // If the bot can't manage the user - errorEmbed(context, "I am unable to unmute this user"); - } - } - } else { // If the member object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } - } -} - -// Send an embed in case of an error -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = unmute; diff --git a/commands/warn.js b/commands/warn.js deleted file mode 100644 index 15d951e..0000000 --- a/commands/warn.js +++ /dev/null @@ -1,86 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class warn extends command { - constructor() { - // Set the run method, description, category, and usage - super("warn"); - super.description = "Warns the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set the required configs - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - } - - // The command's run method - warn(context) { - // If the user has the mod role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.warn.modrole)) { - // Get the user first pinged in the message - const user = context.message.mentions.users.first(); - - // If the user object exists - if (user) { - // Get the guild member object from the user - const member = context.message.guild.member(user); - - // If the member object exists. i.e. if the user is in the server - if (member) { - // Get the part of the argument array which the reason is in - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the array into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setColor(embedColor) - .setTitle("Member Warned") - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into the channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been warned`) - .addField("Reason", reason || "*none*"); - - // Send the embeds - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.warn.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - } - } else { // If the member objest doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user isn't mod - errorEmbed(context, "You do not have permission to run this command"); - } - } -} - -// Send an embed in case of an error -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = warn; diff --git a/data/partner/partner.json b/data/partner/partner.json deleted file mode 100644 index 73c3d34..0000000 --- a/data/partner/partner.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "name": "Cuzethstan", - "description": "Cuzeth Server. Yes.", - "invite": "http://discord.gg/uhEFNw7", - "icon": "https://cdn.discordapp.com/icons/720177983016665251/a_e4250e57b26559c6609dfe562774ee27.gif" - }, - { - "name": "Boblin", - "description": "Official server of the... Boblin?\n- Multiple Topics\n- Lots of Very Active Members", - "invite": "https://discord.gg/Td4uzVu", - "icon": "https://cdn.discordapp.com/attachments/464708407010787328/487824441846267907/image0.png" - } -] \ No newline at end of file diff --git a/events/guildMemberAdd.js b/events/guildMemberAdd.js deleted file mode 100644 index 7b57581..0000000 --- a/events/guildMemberAdd.js +++ /dev/null @@ -1,32 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "member-logs"; - -// Event class -class guildmemberadd extends event { - constructor() { - // Set the event's run method - super("guildmemberadd"); - } - - // Run method - guildmemberadd(member) { - // Create an embed with the user who joined's information - const embed = new MessageEmbed() - .setTitle("Member Joined") - .setColor(embedColor) - .addField("User", `${member} \`${member.user.tag}\``) - .addField("Created", `${member.user.createdAt}`) - .setFooter(`User ID: ${member.user.id}`) - .setThumbnail(member.user.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the mod's log channel - member.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } -} - -module.exports = guildmemberadd; diff --git a/events/guildMemberRemove.js b/events/guildMemberRemove.js deleted file mode 100644 index ed85ef2..0000000 --- a/events/guildMemberRemove.js +++ /dev/null @@ -1,32 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "member-logs"; - -// Event class -class guildmemberremove extends event { - constructor() { - // Set the event's run method - super("guildmemberremove"); - } - - // Run method - guildmemberremove(member) { - // Create an embed with the user's information - const embed = new MessageEmbed() - .setTitle("Member Left") - .setColor(embedColor) - .addField("User", `${member} \`${member.user.tag}\``) - .addField("Joined", `${member.joinedAt}`) - .setFooter(`User ID: ${member.user.id}`) - .setThumbnail(member.user.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the log channel - member.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } -} - -module.exports = guildmemberremove; diff --git a/events/guildMemberUpdate.js b/events/guildMemberUpdate.js deleted file mode 100644 index 806903c..0000000 --- a/events/guildMemberUpdate.js +++ /dev/null @@ -1,41 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "member-logs"; - -// Event class -class guildmemberupdate extends event { - constructor() { - // Set the event's run method - super("guildmemberupdate"); - } - - // Run method - guildmemberupdate(oldMember, newMember) { - // If the user's nickname was changed - if (oldMember.nickname != newMember.nickname) { - // Get the user's name with tag, their old nickname and their new nickname - // If they didn't have a nickname or they removed it, set it to "none" in italics - const oldNickname = oldMember.nickname || "*none*"; - const newNickname = newMember.nickname || "*none*"; - - // Create the embed with the user's information - const embed = new MessageEmbed() - .setTitle("Nickname Changed") - .setColor(embedColor) - .addField("User", `${newMember} \`${newMember.user.tag}\``) - .addField("Before", oldNickname, true) - .addField("After", newNickname, true) - .setFooter(`User ID: ${newMember.user.id}`) - .setThumbnail(newMember.user.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the log channel - newMember.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } - } -} - -module.exports = guildmemberupdate; diff --git a/events/messageDelete.js b/events/messageDelete.js deleted file mode 100644 index 19fea4a..0000000 --- a/events/messageDelete.js +++ /dev/null @@ -1,32 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "message-logs"; - -// Event class -class messagedelete extends event { - constructor() { - // The event's run method - super("messagedelete"); - } - - // Run method - messagedelete(message) { - // Create an embed with the message's information - const embed = new MessageEmbed() - .setTitle("Message Deleted") - .setColor(embedColor) - .addField("User", `${message.author} \`${message.author.tag}\``) - .addField("Channel", message.channel) - .addField("Content", `\`\`\`${message.content || "*none*"}\`\`\``) - .setThumbnail(message.author.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the logging channel - message.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } -} - -module.exports = messagedelete; diff --git a/package.json b/package.json index 5420a50..6874877 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,31 @@ { "name": "vylbot-app", - "version": "2.1.1", - "description": "", - "main": "vylbot.js", + "version": "3.0", + "description": "A discord bot made for Vylpes' Den", + "main": "./dist/vylbot", + "typings": "./dist", "scripts": { - "start": "node vylbot.js", - "lint": "eslint .", - "lint:fix": "eslint . --fix" + "build": "tsc", + "start": "ts-node ./src/vylbot" }, "repository": { "type": "git", "url": "git+https://github.com/Vylpes/vylbot-app.git" }, "author": "Vylpes", - "license": "ISC", - "bugs": { - "url": "https://github.com/Vylpes/vylbot-app/issues" - }, - "homepage": "https://github.com/Vylpes/vylbot-app#readme", + "license": "MIT", + "bugs": "https://github.com/Vylpes/vylbot-app/issues", + "homepage": "https://github.com/Vylpes/vylbot-app", "dependencies": { + "dotenv": "^10.0.0", "emoji-regex": "^9.2.0", - "random-bunny": "^1.0.0", - "vylbot-core": "^1.0.4" + "random-bunny": "^2.0.0", + "vylbot-core": "^2.0.3" }, "devDependencies": { - "eslint": "^7.17.0" + "@types/node": "^16.11.10", + "eslint": "^7.17.0", + "ts-node": "^10.4.0", + "typescript": "^4.5.2" } } diff --git a/src/commands/about.ts b/src/commands/about.ts new file mode 100644 index 0000000..f30e490 --- /dev/null +++ b/src/commands/about.ts @@ -0,0 +1,19 @@ +import { Command, ICommandContext } from "vylbot-core"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class About extends Command { + constructor() { + super(); + super._category = "General"; + } + + public override execute(context: ICommandContext) { + const embed = new PublicEmbed(context, "About", "") + .addField("Version", process.env.BOT_VER) + .addField("VylBot Core", process.env.CORE_VER) + .addField("Author", process.env.BOT_AUTHOR) + .addField("Date", process.env.BOT_DATE); + + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/ban.ts b/src/commands/ban.ts new file mode 100644 index 0000000..b0a1371 --- /dev/null +++ b/src/commands/ban.ts @@ -0,0 +1,61 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import ErrorMessages from "../constants/ErrorMessages"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Bane extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1) + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.bannable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Banned"); + logEmbed.AddUser("User", targetUser, true); + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been banned`); + + await targetMember.ban({ reason: reason }); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/clear.ts b/src/commands/clear.ts new file mode 100644 index 0000000..14015d4 --- /dev/null +++ b/src/commands/clear.ts @@ -0,0 +1,36 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import { TextChannel } from "discord.js"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Clear extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + if (context.args.length == 0) { + const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const totalToClear = Number.parseInt(context.args[0]); + + if (!totalToClear || totalToClear <= 0 || totalToClear > 100) { + const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100"); + errorEmbed.SendToCurrentChannel(); + return; + } + + await (context.message.channel as TextChannel).bulkDelete(totalToClear); + + const embed = new PublicEmbed(context, "", `${totalToClear} message(s) were removed`); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/eval.ts b/src/commands/eval.ts new file mode 100644 index 0000000..0c8154f --- /dev/null +++ b/src/commands/eval.ts @@ -0,0 +1,32 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Evaluate extends Command { + constructor() { + super(); + + super._category = "Owner"; + } + + public override execute(context: ICommandContext) { + if (context.message.author.id != process.env.BOT_OWNERID) { + return; + } + + const stmt = context.args; + + console.log(`Eval Statement: ${stmt.join(" ")}`); + + try { + const result = eval(stmt.join(" ")); + + const embed = new PublicEmbed(context, "", result); + embed.SendToCurrentChannel(); + } + catch (err: any) { + const errorEmbed = new ErrorEmbed(context, err); + errorEmbed.SendToCurrentChannel(); + } + } +} \ No newline at end of file diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..dc56258 --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,118 @@ +import { existsSync, readdirSync } from "fs"; +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; +import StringTools from "../helpers/StringTools"; + +interface ICommandData { + Exists: boolean; + Name?: string; + Category?: string; + Roles?: string[]; +} + +export default class Help extends Command { + constructor() { + super(); + + super._category = "General"; + } + + public override execute(context: ICommandContext) { + if (context.args.length == 0) { + this.SendAll(context); + } else { + this.SendSingle(context); + } + } + + private SendAll(context: ICommandContext) { + const allCommands = this.GetAllCommandData(); + const cateogries = this.DetermineCategories(allCommands); + + const embed = new PublicEmbed(context, "Commands", ""); + + cateogries.forEach(category => { + let filtered = allCommands.filter(x => x.Category == category); + + embed.addField(StringTools.Capitalise(category), filtered.flatMap(x => x.Name).join(", ")); + }); + + embed.SendToCurrentChannel(); + } + + private SendSingle(context: ICommandContext) { + const command = this.GetCommandData(context.args[0]); + + if (!command.Exists) { + const errorEmbed = new ErrorEmbed(context, "Command does not exist"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const embed = new PublicEmbed(context, StringTools.Capitalise(command.Name!), ""); + embed.addField("Category", StringTools.Capitalise(command.Category!)); + embed.addField("Required Roles", StringTools.Capitalise(command.Roles!.join(", ")) || "*none*"); + + embed.SendToCurrentChannel(); + } + + private GetAllCommandData(): ICommandData[] { + const result: ICommandData[] = []; + + const folder = process.env.FOLDERS_COMMANDS!; + + const contents = readdirSync(`${process.cwd()}/${folder}`); + + contents.forEach(name => { + const file = require(`${process.cwd()}/${folder}/${name}`).default; + const command = new file() as Command; + + const data: ICommandData = { + Exists: true, + Name: name.replace(".ts", ""), + Category: command._category || "none", + Roles: command._roles, + }; + + result.push(data); + }); + + return result; + } + + private GetCommandData(name: string): ICommandData { + const folder = process.env.FOLDERS_COMMANDS!; + const path = `${process.cwd()}/${folder}/${name}.ts`; + + if (!existsSync(path)) { + return { + Exists: false + }; + } + + const file = require(path).default; + const command = new file() as Command; + + const data: ICommandData = { + Exists: true, + Name: name, + Category: command._category || "none", + Roles: command._roles + }; + + return data; + } + + private DetermineCategories(commands: ICommandData[]): string[] { + const result: string[] = []; + + commands.forEach(cmd => { + if (!result.includes(cmd.Category!)) { + result.push(cmd.Category!); + } + }); + + return result; + } +} \ No newline at end of file diff --git a/src/commands/kick.ts b/src/commands/kick.ts new file mode 100644 index 0000000..1d9123f --- /dev/null +++ b/src/commands/kick.ts @@ -0,0 +1,61 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Kick extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1) + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.kickable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Kicked"); + logEmbed.AddUser("User", targetUser, true); + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been kicked`); + + await targetMember.kick(reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/mute.ts b/src/commands/mute.ts new file mode 100644 index 0000000..774e437 --- /dev/null +++ b/src/commands/mute.ts @@ -0,0 +1,70 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Mute extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1); + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.manageable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Muted"); + logEmbed.AddUser("User", targetUser, true) + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been muted`); + publicEmbed.AddReason(reason); + + const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED); + + if (!mutedRole) { + const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound); + embed.SendToCurrentChannel(); + return; + } + + await targetMember.roles.add(mutedRole, reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/poll.ts b/src/commands/poll.ts new file mode 100644 index 0000000..3d53194 --- /dev/null +++ b/src/commands/poll.ts @@ -0,0 +1,56 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Poll extends Command { + constructor() { + super(); + + super._category = "General"; + } + + public override async execute(context: ICommandContext) { + const argsJoined = context.args.join(" "); + const argsSplit = argsJoined.split(";"); + + if (argsSplit.length < 3 || argsSplit.length > 10) { + const errorEmbed = new ErrorEmbed(context, "Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const title = argsSplit[0]; + + const arrayOfNumbers = [ + ':one:', + ':two:', + ':three:', + ':four:', + ':five:', + ':six:', + ':seven:', + ':eight:', + ':nine:' + ]; + + const reactionEmojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]; + + const description = arrayOfNumbers.splice(0, argsSplit.length - 1); + + description.forEach((value, index) => { + description[index] = `${value} ${argsSplit[index + 1]}`; + }); + + const embed = new PublicEmbed(context, title, description.join("\n")); + + const message = await context.message.channel.send(embed); + + description.forEach(async (value, index) => { + await message.react(reactionEmojis[index]); + }); + + if (context.message.deletable) { + await context.message.delete({ reason: "Poll command" }); + } + } +} \ No newline at end of file diff --git a/src/commands/role.ts b/src/commands/role.ts new file mode 100644 index 0000000..53c15d2 --- /dev/null +++ b/src/commands/role.ts @@ -0,0 +1,69 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; +import { Role as DiscordRole } from "discord.js"; + +export default class Role extends Command { + constructor() { + super(); + + super._category = "General"; + } + + public override execute(context: ICommandContext) { + const roles = process.env.COMMANDS_ROLE_ROLES!.split(','); + + if (context.args.length == 0) { + this.SendRolesList(context, roles); + } else { + this.ToggleRole(context, roles); + } + } + + private SendRolesList(context: ICommandContext, roles: String[]) { + const description = `Do ${process.env.BOT_PREFIX}role <role> to get the role!\n${roles.join('\n')}`; + + const embed = new PublicEmbed(context, "Roles", description); + embed.SendToCurrentChannel(); + } + + private ToggleRole(context: ICommandContext, roles: String[]) { + const requestedRole = context.args[0]; + + if (!roles.includes(requestedRole)) { + const errorEmbed = new ErrorEmbed(context, "This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const assignRole = context.message.guild?.roles.cache.find(x => x.name == requestedRole); + + if (!assignRole) { + const errorEmbed = new ErrorEmbed(context, "The current server doesn't have this role. Please contact the server's moderators"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const role = context.message.member?.roles.cache.find(x => x.name == requestedRole) + + if (!role) { + this.AddRole(context, assignRole); + } else { + this.RemoveRole(context, assignRole); + } + } + + private async AddRole(context: ICommandContext, role: DiscordRole) { + await context.message.member?.roles.add(role); + + const embed = new PublicEmbed(context, "", `Gave role: ${role.name}`); + embed.SendToCurrentChannel(); + } + + private async RemoveRole(context: ICommandContext, role: DiscordRole) { + await context.message.member?.roles.remove(role); + + const embed = new PublicEmbed(context, "", `Removed role: ${role.name}`); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/rules.ts b/src/commands/rules.ts new file mode 100644 index 0000000..9b9af9f --- /dev/null +++ b/src/commands/rules.ts @@ -0,0 +1,46 @@ +import { existsSync, readFileSync } from "fs"; +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +interface IRules { + title?: string; + description?: string[]; + image?: string; + footer?: string; +} + +export default class Rules extends Command { + constructor() { + super(); + + super._category = "Admin"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override execute(context: ICommandContext) { + if (!existsSync(process.env.COMMANDS_RULES_FILE!)) { + const errorEmbed = new ErrorEmbed(context, "Rules file doesn't exist"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const rulesFile = readFileSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE}`).toString(); + const rules = JSON.parse(rulesFile) as IRules[]; + + const embeds: PublicEmbed[] = []; + + rules.forEach(rule => { + const embed = new PublicEmbed(context, rule.title || "", rule.description?.join("\n") || ""); + + embed.setImage(rule.image || ""); + embed.setFooter(rule.footer || ""); + + embeds.push(embed); + }); + + embeds.forEach(x => x.SendToCurrentChannel()); + } +} \ No newline at end of file diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts new file mode 100644 index 0000000..07fb03a --- /dev/null +++ b/src/commands/unmute.ts @@ -0,0 +1,70 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Unmute extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1); + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.manageable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Unmuted"); + logEmbed.AddUser("User", targetUser, true) + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been unmuted`); + publicEmbed.AddReason(reason); + + const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED); + + if (!mutedRole) { + const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound); + embed.SendToCurrentChannel(); + return; + } + + await targetMember.roles.remove(mutedRole, reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/warn.ts b/src/commands/warn.ts new file mode 100644 index 0000000..fdd186f --- /dev/null +++ b/src/commands/warn.ts @@ -0,0 +1,53 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Warn extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override execute(context: ICommandContext) { + const user = context.message.mentions.users.first(); + + if (!user) { + const errorEmbed = new ErrorEmbed(context, "Please specify a valid user"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const member = context.message.guild?.member(user); + + if (!member) { + const errorEmbed = new ErrorEmbed(context, "Please specify a valid user"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1); + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + const logEmbed = new LogEmbed(context, "Member Warned"); + logEmbed.AddUser("User", user, true); + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${user} has been warned`); + publicEmbed.AddReason(reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/constants/ErrorMessages.ts b/src/constants/ErrorMessages.ts new file mode 100644 index 0000000..a397dcf --- /dev/null +++ b/src/constants/ErrorMessages.ts @@ -0,0 +1,5 @@ +export default class ErrorMessages { + public static readonly InsufficientBotPermissions = "Unable to do this action, am I missing permissions?"; + public static readonly ChannelNotFound = "Unable to find channel"; + public static readonly RoleNotFound = "Unable to find role"; +} \ No newline at end of file diff --git a/src/events/MemberEvents.ts b/src/events/MemberEvents.ts new file mode 100644 index 0000000..696fa11 --- /dev/null +++ b/src/events/MemberEvents.ts @@ -0,0 +1,36 @@ +import { Event } from "vylbot-core"; +import { GuildMember } from "discord.js"; +import EventEmbed from "../helpers/EventEmbed"; +import GuildMemberUpdate from "./MemberEvents/GuildMemberUpdate"; + +export default class MemberEvents extends Event { + constructor() { + super(); + } + + public override guildMemberAdd(member: GuildMember) { + const embed = new EventEmbed(member.guild, "Member Joined"); + embed.AddUser("User", member.user, true); + embed.addField("Created", member.user.createdAt); + embed.setFooter(`Id: ${member.user.id}`); + + embed.SendToMemberLogsChannel(); + } + + public override guildMemberRemove(member: GuildMember) { + const embed = new EventEmbed(member.guild, "Member Left"); + embed.AddUser("User", member.user, true); + embed.addField("Joined", member.joinedAt); + embed.setFooter(`Id: ${member.user.id}`); + + embed.SendToMemberLogsChannel(); + } + + public override guildMemberUpdate(oldMember: GuildMember, newMember: GuildMember) { + const handler = new GuildMemberUpdate(oldMember, newMember); + + if (oldMember.nickname != newMember.nickname) { // Nickname change + handler.NicknameChanged(); + } + } +} \ No newline at end of file diff --git a/src/events/MemberEvents/GuildMemberUpdate.ts b/src/events/MemberEvents/GuildMemberUpdate.ts new file mode 100644 index 0000000..ef2c530 --- /dev/null +++ b/src/events/MemberEvents/GuildMemberUpdate.ts @@ -0,0 +1,25 @@ +import { GuildMember } from "discord.js"; +import EventEmbed from "../../helpers/EventEmbed"; + +export default class GuildMemberUpdate { + private _oldMember: GuildMember; + private _newMember: GuildMember; + + constructor(oldMember: GuildMember, newMember: GuildMember) { + this._oldMember = oldMember; + this._newMember = newMember; + } + + public NicknameChanged() { + const oldNickname = this._oldMember.nickname || "*none*"; + const newNickname = this._newMember.nickname || "*none*"; + + const embed = new EventEmbed(this._newMember.guild, "Nickname Changed"); + embed.AddUser("User", this._newMember.user, true); + embed.addField("Before", oldNickname, true); + embed.addField("After", newNickname, true); + embed.setFooter(`Id: ${this._newMember.user.id}`); + + embed.SendToMemberLogsChannel(); + } +} \ No newline at end of file diff --git a/src/events/MessageEvents.ts b/src/events/MessageEvents.ts new file mode 100644 index 0000000..0d449ec --- /dev/null +++ b/src/events/MessageEvents.ts @@ -0,0 +1,35 @@ +import { Event } from "vylbot-core"; +import { Message } from "discord.js"; +import EventEmbed from "../helpers/EventEmbed"; + +export default class MessageEvents extends Event { + constructor() { + super(); + } + + public override messageDelete(message: Message) { + if (!message.guild) return; + + const embed = new EventEmbed(message.guild, "Message Deleted"); + embed.AddUser("User", message.author, true); + embed.addField("Channel", message.channel, true); + embed.addField("Content", `\`\`\`${message.content || "*none*"}\`\`\``); + embed.addField("Attachments", `\`\`\`${message.attachments.map(x => x.url).join("\n")}`); + + embed.SendToMessageLogsChannel(); + } + + public override messageUpdate(oldMessage: Message, newMessage: Message) { + if (!newMessage.guild) return; + if (newMessage.author.bot) return; + if (oldMessage.content == newMessage.content) return; + + const embed = new EventEmbed(newMessage.guild, "Message Edited"); + embed.AddUser("User", newMessage.author, true); + embed.addField("Channel", newMessage.channel, true); + embed.addField("Before", `\`\`\`${oldMessage.content || "*none*"}\`\`\``); + embed.addField("After", `\`\`\`${newMessage.content || "*none*"}\`\`\``); + + embed.SendToMessageLogsChannel(); + } +} \ No newline at end of file diff --git a/src/helpers/ErrorEmbed.ts b/src/helpers/ErrorEmbed.ts new file mode 100644 index 0000000..9964369 --- /dev/null +++ b/src/helpers/ErrorEmbed.ts @@ -0,0 +1,19 @@ +import { MessageEmbed } from "discord.js"; +import { ICommandContext } from "vylbot-core"; + +export default class ErrorEmbed extends MessageEmbed { + private _context: ICommandContext; + + constructor(context: ICommandContext, message: String) { + super(); + + super.setColor(process.env.EMBED_COLOUR_ERROR!); + super.setDescription(message); + + this._context = context; + } + + public SendToCurrentChannel() { + this._context.message.channel.send(this); + } +} \ No newline at end of file diff --git a/src/helpers/EventEmbed.ts b/src/helpers/EventEmbed.ts new file mode 100644 index 0000000..25141d8 --- /dev/null +++ b/src/helpers/EventEmbed.ts @@ -0,0 +1,54 @@ +import { MessageEmbed, TextChannel, User, Guild } from "discord.js"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "./ErrorEmbed"; + +export default class EventEmbed extends MessageEmbed { + private _guild: Guild; + + constructor(guild: Guild, title: string) { + super(); + + super.setColor(process.env.EMBED_COLOUR!); + super.setTitle(title); + + this._guild = guild; + } + + // Detail methods + public AddUser(title: string, user: User, setThumbnail: boolean = false) { + super.addField(title, `${user} \`${user.tag}\``, true); + + if (setThumbnail) { + super.setThumbnail(user.displayAvatarURL()); + } + } + + public AddReason(message: String) { + super.addField("Reason", message || "*none*"); + } + + // Send methods + public SendToChannel(name: string) { + const channel = this._guild.channels.cache + .find(channel => channel.name == name) as TextChannel; + + if (!channel) { + console.error(`Unable to find channel ${name}`); + return; + } + + channel.send(this); + } + + public SendToMessageLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MESSAGE!) + } + + public SendToMemberLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MEMBER!) + } + + public SendToModLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MOD!) + } +} \ No newline at end of file diff --git a/src/helpers/LogEmbed.ts b/src/helpers/LogEmbed.ts new file mode 100644 index 0000000..c96acdc --- /dev/null +++ b/src/helpers/LogEmbed.ts @@ -0,0 +1,60 @@ +import { MessageEmbed, TextChannel, User } from "discord.js"; +import { ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "./ErrorEmbed"; + +export default class LogEmbed extends MessageEmbed { + private _context: ICommandContext; + + constructor(context: ICommandContext, title: string) { + super(); + + super.setColor(process.env.EMBED_COLOUR!); + super.setTitle(title); + + this._context = context; + } + + // Detail methods + public AddUser(title: string, user: User, setThumbnail: boolean = false) { + super.addField(title, `${user} \`${user.tag}\``, true); + + if (setThumbnail) { + super.setThumbnail(user.displayAvatarURL()); + } + } + + public AddReason(message: String) { + super.addField("Reason", message || "*none*"); + } + + // Send methods + public SendToCurrentChannel() { + this._context.message.channel.send(this); + } + + public SendToChannel(name: string) { + const channel = this._context.message.guild?.channels.cache + .find(channel => channel.name == name) as TextChannel; + + if (!channel) { + const errorEmbed = new ErrorEmbed(this._context, ErrorMessages.ChannelNotFound); + errorEmbed.SendToCurrentChannel(); + return; + } + + channel.send(this); + } + + public SendToMessageLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MESSAGE!) + } + + public SendToMemberLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MEMBER!) + } + + public SendToModLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MOD!) + } +} \ No newline at end of file diff --git a/src/helpers/PublicEmbed.ts b/src/helpers/PublicEmbed.ts new file mode 100644 index 0000000..da278e5 --- /dev/null +++ b/src/helpers/PublicEmbed.ts @@ -0,0 +1,26 @@ +import { MessageEmbed } from "discord.js"; +import { ICommandContext } from "vylbot-core"; + +export default class PublicEmbed extends MessageEmbed { + private _context: ICommandContext; + + constructor(context: ICommandContext, title: string, description: string) { + super(); + + super.setColor(process.env.EMBED_COLOUR!); + super.setTitle(title); + super.setDescription(description); + + this._context = context; + } + + // Detail methods + public AddReason(message: String) { + super.addField("Reason", message || "*none*"); + } + + // Send methods + public SendToCurrentChannel() { + this._context.message.channel.send(this); + } +} \ No newline at end of file diff --git a/src/helpers/StringTools.ts b/src/helpers/StringTools.ts new file mode 100644 index 0000000..b42eb90 --- /dev/null +++ b/src/helpers/StringTools.ts @@ -0,0 +1,15 @@ +export default class StringTools { + public static Capitalise(str: string): string { + const words = str.split(" "); + let result: string[] = []; + + words.forEach(word => { + const firstLetter = word.substring(0, 1).toUpperCase(); + const rest = word.substring(1); + + result.push(firstLetter + rest); + }); + + return result.join(" "); + } +} \ No newline at end of file diff --git a/src/vylbot.ts b/src/vylbot.ts new file mode 100644 index 0000000..80f7a25 --- /dev/null +++ b/src/vylbot.ts @@ -0,0 +1,25 @@ +import { CoreClient } from "vylbot-core"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +const requiredConfigs = [ + "EMBED_COLOUR", + "EMBED_COLOUR_ERROR", + "ROLES_MODERATOR", + "ROLES_MUTED", + "CHANNELS_LOGS_MESSAGE", + "CHANNELS_LOGS_MEMBER", + "CHANNELS_LOGS_MOD", + "COMMANDS_ROLE_ROLES", + "COMMANDS_RULES_FILE" +]; + +requiredConfigs.forEach(config => { + if (!process.env[config]) { + throw `${config} is required in .env`; + } +}); + +const client = new CoreClient(); +client.start(); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3f030e6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,78 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": [ + "./src", + ], + "exclude": [ + "./tests" + ] +} diff --git a/vylbot.js b/vylbot.js deleted file mode 100644 index d8625ff..0000000 --- a/vylbot.js +++ /dev/null @@ -1,5 +0,0 @@ -const vylbot = require('vylbot-core'); -const config = require('./config.json'); - -const client = new vylbot.client(config); -client.start(); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e3e84ea..1b8040a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,18 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@discordjs/collection@^0.1.6": version "0.1.6" resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c" @@ -66,6 +78,77 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@sindresorhus/is@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.2.0.tgz#667bfc6186ae7c9e0b45a08960c551437176e1ca" + integrity sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw== + +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + +"@types/cacheable-request@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" + integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + +"@types/http-cache-semantics@*": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" + integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== + +"@types/keyv@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41" + integrity sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "16.11.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.11.tgz#6ea7342dfb379ea1210835bada87b3c512120234" + integrity sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw== + +"@types/node@^16.11.10": + version "16.11.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.10.tgz#2e3ad0a680d96367103d3e670d41c2fed3da61ae" + integrity sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA== + +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -78,11 +161,21 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.4.1: + version "8.6.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" + integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -127,6 +220,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -157,6 +255,24 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -179,6 +295,13 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -215,6 +338,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -231,16 +359,33 @@ debug@^4.0.1, debug@^4.1.1: dependencies: ms "2.1.2" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + discord.js@^12.3.1: version "12.5.3" resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.5.3.tgz#56820d473c24320871df9ea0bbc6b462f21cf85c" @@ -262,6 +407,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -272,6 +422,13 @@ emoji-regex@^9.2.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -453,6 +610,13 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -486,6 +650,23 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" +got@^11.8.3: + version "11.8.3" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" + integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -496,6 +677,19 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -562,6 +756,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -577,6 +776,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +keyv@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.4.tgz#f040b236ea2b06ed15ed86fbef8407e1a1c8e376" + integrity sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg== + dependencies: + json-buffer "3.0.1" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -595,6 +801,11 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -602,6 +813,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + mime-db@1.51.0: version "1.51.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" @@ -614,6 +830,16 @@ mime-types@^2.1.12: dependencies: mime-db "1.51.0" +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -638,7 +864,12 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" -once@^1.3.0: +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -657,6 +888,11 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -689,18 +925,31 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -random-bunny@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/random-bunny/-/random-bunny-1.2.2.tgz#821882ba33b6be05e3a538c43e822e5e9896862d" - integrity sha512-m12WIb4uNeEA8eLk9vxK6U9x/+KlRwBqOoiuGku6C6kF6DcbyMTq0RaRUXF3fXxpRtZbpoU7dgttLoETTXXL/g== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +random-bunny@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/random-bunny/-/random-bunny-2.0.0.tgz#f9950f53c7f5183a6dad26386db14f702d477cf7" + integrity sha512-xIDaPghs0nslKWNfxDLAGNtsUPUlmNagUmdNAHP7U4LSrGySbgSfAzCV8eECTeuny2csmf0SL5dBxEZdVvHgOA== dependencies: glob-parent "^6.0.0" - node-fetch "^2.6.1" + got "^11.8.3" regexpp@^3.1.0: version "3.2.0" @@ -712,11 +961,23 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -818,6 +1079,24 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +ts-node@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" + integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + yn "3.1.1" + tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" @@ -835,6 +1114,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +typescript@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" + integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -847,12 +1131,13 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vylbot-core@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-1.0.5.tgz#44a0a38d3c8bfda38883cab1a16e5e15435c98d7" - integrity sha512-N86HpZnQadi+d4w4ZaTcbSRAjRInhzG8BN/WDNGUvFjF2UuA8SUKfd9Wk6rbs7mLLyXw+VFFLYdDbe0Q6ZfNYA== +vylbot-core@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-2.0.3.tgz#e7d844bc54e72b14f06f14b63866854e3b846d55" + integrity sha512-RxABRcwhVIVZcIssVP8yosLlUXzcdrNLiYJzcvkAIz4KeHfoARcxjsz2vG2x9GIzeFpfn/hjrPvjFC5lN6zclw== dependencies: discord.js "^12.3.1" + dotenv "^10.0.0" webidl-conversions@^3.0.0: version "3.0.1" @@ -893,3 +1178,8 @@ yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==