diff --git a/.eslintrc b/.eslintrc index 4b31bff..d27c3a5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,7 +15,12 @@ "client": false, "commandCharacter": false, "discord": false, "fs": false, "discordCommandHandler": false, "request": false, "path": false, "Storage": false, "Tools": false, "MessageParser": false, "DiscordCommandHandler": false, "Users": false, "Config": false, "packagejson": false, "discordConfig": false, "failureEmoji": false, "successEmoji": false, - "DiscordMessageParser": false, "discordMessageParser": false, "discordText": false, "moodeText": false, "showdownText": false + "DiscordMessageParser": false, "discordMessageParser": false, "discordText": false, "moodeText": false, "showdownText": false, + "pokemonShowdownText": false, "psClient": false, "psConfig": false, "psRooms": false, "psUsers": false, "PsCommandHandler": false, + "psCommandHandler": false, "psMessageParser": false, "Tournaments": false, "runDiscord": false, "runShowdown": false, "DiscordEditRules": + false, "discordEditRules": false, "hash": false, "discordSuccessEmoji": false, "discordFailureEmoji": false, "runTwitch": false, + "twitch": false, "twitchConfig": false, "bot": false, "DiscordPlugins": false, "TwitchCommandHandler": false, "twitchCommandHandler": false, + "TwitchMessageParser": false, "twitchMessageParser": false, "TwitchPlugins": false, "DiscordReactionHandler": false, "discordReactionHandler": false }, "extends": "eslint:recommended", "rules": { @@ -72,7 +77,7 @@ "no-unmodified-loop-condition": "error", "no-unused-expressions": "error", "no-useless-call": "error", - "no-useless-concat": "off", + "no-useless-concat": "error", "no-void": "off", "no-warning-comments": "off", "no-with": "error", @@ -142,6 +147,7 @@ "operator-assignment": "off", "operator-linebreak": ["error", "after"], "padded-blocks": ["error", "never"], + "prefer-template": "error", "quote-props": "off", "quotes": ["error", "double", { "allowTemplateLiterals": true }], "require-jsdoc": "off", @@ -168,7 +174,7 @@ "no-restricted-syntax": ["error", "WithStatement"], "prefer-const": "error", - "no-self-assign": ["error", {"props": false}], + "no-self-assign": ["error", {"props": false}], "no-constant-condition": ["error", {"checkLoops": false}] } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index e5e7517..e8e9284 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [12.x] + node-version: [15.x] steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index c2e2b37..640f914 100644 --- a/.gitignore +++ b/.gitignore @@ -8,16 +8,36 @@ node_modules/ converter.js +/assets/private/* + /databases/* !/databases/.dummy /discord/config.json -/discord/.twitchMonitor.js -/discord/twitchMonitor.js /discord/commands/nsfw/* !/discord/commands/nsfw/command.js.example /discord/commands/joke/* !/discord/commands/joke/command.js.example +/discord/plugins/* +!/discord/plugins/backup.js /discord/rules/* !/discord/rules/link-message.js +!/discord/rules/get-ps-battle-info.js +!/discord/rules/filter.js !/discord/rules/rule.js.example +!/discord/rules/editRules +/discord/rules/editRules/* +!/discord/rules/editRules/editRule.js.example +!/discord/rules/editRules/filter.js + +/showdown/config.json +/showdown/commands/private/* +!/showdown/commands/private/command.js.example +/showdown/rules/* +!/showdown/rules/rule.js.example + +/twitch/config.json +/twitch/commands/private/* +!/twitch/commands/private/command.js.example +/twitch/rules/* +!/twitch/rules/rule.js.example diff --git a/README.md b/README.md index 969b69d..688d493 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # moodE -![https://github.com/LegoFigure11/moodE/actions](https://github.com/LegoFigure11/moodE/workflows/Node.js%20CI/badge.svg) +[](https://github.com/LegoFigure11/moodE/actions) [](https://spo.ink/moodedev) ## About -moodE is a competitive Pokémon-focused chat bot for Discord ~~, Pokemon Showdown, and Twitch~~, written in nodejs. It is based on [JsKingBoo](https://github.com/JsKingBoo)'s [SableyeBot](https://github.com/JsKingBoo/SableyeBot3/), [DragonWhale](https://github.com/DragonWhale)'s [BattleSpotBot](https://github.com/DragonWhale/BattleSpotBot), and [sirDonovan](https://github.com/sirDonovan/)'s [Lanette](https://github.com/sirDonovan/Lanette) and [Cassius](https://github.com/sirDonovan/Cassius). It parses data from [pokemon-showdown](https://github.com/smogon/pokemon-showdown), and some of the commands use code from there. +[](https://spo.ink/moodedev) + +moodE is a competitive Pokémon-focused chat bot for Discord, Pokemon Showdown, ~~and Twitch~~, written in nodejs. It is based on [JsKingBoo](https://github.com/JsKingBoo)'s [SableyeBot](https://github.com/JsKingBoo/SableyeBot3/), [DragonWhale](https://github.com/DragonWhale)'s [BattleSpotBot](https://github.com/DragonWhale/BattleSpotBot), and [sirDonovan](https://github.com/sirDonovan/)'s [Lanette](https://github.com/sirDonovan/Lanette) and [Cassius](https://github.com/sirDonovan/Cassius). It parses data from [pokemon-showdown](https://github.com/smogon/pokemon-showdown), and some of the commands use code from there. ## Installation @@ -33,7 +35,9 @@ From here, install the dependencies from `package.json` (you can ignore the warn 3. Set up config files -Currently, only Discord is supported by this bot (Twitch and Pokemon Showdown are in the works!). The configuration file for discord is found in `/discord/config.json`, and you will need to create this file by copying `/discord/config-example.json` and renaming it to `config.json`. You will then need to edit this file to include your [bot token](https://www.writebots.com/discord-bot-token/), amongst other things. +Currently, Discord and Pokemon Showdown are supported by this bot (Twitch is in the works!). + +3a. The configuration file for discord is found in `/discord/config.json`, and you will need to create this file by copying `/discord/config-example.json` and renaming it to `config.json`. You will then need to edit this file to include your [bot token](https://www.writebots.com/discord-bot-token/), amongst other things. | Field | Value | Description | | -----:|:------|-------------| @@ -45,9 +49,25 @@ Currently, only Discord is supported by this bot (Twitch and Pokemon Showdown ar | defaultGen | number (1-8) | The default generation to run Dex commands with, if none is specified. | | successEmoji (optional) | "<:string:emojiID>" | Emoji that the bot will use as a success symbol in all servers. See `/assets/greentick.png` for an example. You can upload this image as an emoji to any server that the bot is in, as bot accounts have Nitro by default. | | failureEmoji (optional) | "<:string:emojiID>" | Emoji that the bot will use as an error symbol in all servers. See `/assets/redcross.png` for an example. You can upload this image as an emoji to any server that the bot is in, as bot accounts have Nitro by default.| +| logChannel (optional) | "string" | ID of channel where errors and other disgnostic messages will be logged. | +| backups.channel (optional) | "string" | ID of channel where the bot should automatically back up databases to. | +| backups.interval (optional) | number | Interval (in ms) that the bot should perform a database backup | Some additional configuration can be done in `/moode.js` and command-specific configuration can be done later (once the bot is running). +3b. The configuration file for Pokemon Showdown is found in `/showdown/config.json`, and you will need to create this file by copying `/showdown/config-example.json` and renaming it to `config.json`. You will then need to edit this file to contain your bot's login details, amongst other things. + +| Field | Value | Description | +| -----:|:------|-------------| +| username | "string" | The username of the bot account. | +| password | "string" | The password of the bot account. | +| developers | ["array", "of", "strings"] | A list of the users who will have access to all commands. | +| tournaments | ["array", "of", "strings"] | A list of the rooms in which tournament parsing is enabled. | +| rooms | ["array", "of", "strings"] | A list of the rooms for the bot to join. | +| commandCharacter | "string" | The character that the bot will use to recognise commands. | +| allowMail | boolean | Toggles enabling mail commands. | +| server | "string" | Server for the bot to join (leave this out of your config file to connect to main). | + 4. Starting up the bot If you've done all of the above, you're ready to go! Simply run: @@ -72,6 +92,12 @@ This bot builds upon the work of and wouldn't be possible without: * [tmagicturtle](https://github.com/tmagicturtle/) * Guangcong Luo ([Zarel](https://github.com/Zarel)) and [contributors](https://github.com/smogon/pokemon-showdown/graphs/contributors) +And, of course, my amazing [direct contributors](https://github.com/LegoFigure11/moodE/graphs/contributors)! + +Additionally, I would like to thank the following people for their translation work (currently used in `/showdown/commands/chinese`): + +* [bobochan](https://www.smogon.com/forums/members/271968/) and [others](https://pastebin.com/raw/WLHef9D7) + ## License This software is distributed under the MIT license. For more details, see the `LICENSE` file. diff --git a/discord/app.js b/discord/app.js index 7802a04..c0bdbd1 100644 --- a/discord/app.js +++ b/discord/app.js @@ -1,8 +1,11 @@ "use strict"; +const path = require("path"); + const discordConfig = require("./config.json"); const utilities = require("./utilities.js"); +// Prevent handlers from firing before client is ready let listen = false; client.on("ready", (async () => { @@ -16,28 +19,45 @@ client.on("ready", (async () => { } } - try { - fs.accessSync(path.resolve(__dirname, "./twitchMonitor.js")); - const twitchMonitor = require("./twitchMonitor.js"); - console.log(`${discordText}Loading ${"TwitchMonitor".cyan}...`); - twitchMonitor.monitorTwitch(); - console.log(`${discordText}${"TwitchMonitor".cyan} loaded!`); - } catch (e) {} + global.DiscordCommandHandler = require("./commandHandler.js"); + global.discordCommandHandler = new DiscordCommandHandler(); + await discordCommandHandler.init(); + + global.DiscordEditRules = require("./editRules.js"); + global.discordEditRules = new DiscordEditRules(); + await discordEditRules.init(); global.DiscordMessageParser = require("./messageParser.js"); global.discordMessageParser = new DiscordMessageParser(); await discordMessageParser.init(); - global.DiscordCommandHandler = require("./commandHandler.js"); - global.discordCommandHandler = new DiscordCommandHandler(); - await discordCommandHandler.init(); + global.DiscordReactionHandler = require("./reactionHandler.js"); + global.discordReactionHandler = new DiscordReactionHandler(); + await discordReactionHandler.init(); + + // From https://github.com/sirDonovan/Cassius/blob/master/app.js#L46 + let pluginsList; + const plugins = fs.readdirSync(path.resolve(`${__dirname}/plugins`)); + for (let i = 0, len = plugins.length; i < len; i++) { + const fileName = plugins[i]; + if (!fileName.endsWith(".js")) continue; + if (!pluginsList) pluginsList = []; + const file = require(`./plugins/${fileName}`); + if (file.name && !file.disabled) { + global[file.name] = file; + if (typeof file.onLoad === "function") file.onLoad(); + } + pluginsList.push(file); + } + + global.DiscordPlugins = pluginsList; - console.log(`${discordText}--------------`); - console.log(`${discordText}${"Ready!".green}`); - console.log(`${discordText}--------------`); + console.log(`${Tools.discordText()}--------------`); + console.log(`${Tools.discordText()}${"Ready!".green}`); + console.log(`${Tools.discordText()}--------------`); if (discordConfig.logChannel) { - client.channels.cache.get(discordConfig.logChannel).send(`Bot online! ${discordConfig.commandCharacter === "~" ? "(Local)" : "(Production)"}`); + client.channels.cache.get(discordConfig.logChannel).send(`Bot online! ${discordConfig.commandCharacter === "~" ? "(Local)" : "(Production)"} | Discord: ${runDiscord} | PS: ${runShowdown}`); } listen = true; @@ -53,30 +73,130 @@ client.on("guildCreate", guild => { } }); -client.on("message", message => { +client.on("message", async (message) => { if (!listen) return; - if (message.author.bot) return; // Don't respond to bots + + if (message.author.id === client.user.id) return; // Don't respond to self if (message.channel.type !== "dm") { - discordMessageParser.process(message); + message = await discordMessageParser.process(message); } + // Messages can be deleted by messageParser rules, don't process commands containing banned words or messages that have been otherwise deleted + if (message.deleted) return; + + if (message.author.bot) return; // Don't respond to bots + if (message.content.startsWith(discordConfig.commandCharacter)) { resolveMessage(message); } }); -client.on("messageDelete", async (message) => { +client.on("messageDeleteBulk", async (messages) => { if (!listen) return; + + /*for (const msg of messages) { + let message = msg[1]; + + if (message.partial) { + try { + message = await message.fetch(); + } catch (e) { + console.log(`${Tools.discordText()}Unable to retreive a deleted message! ${(`(ID: ${message.id})`).grey}`); + continue; + } + } + + const db = Storage.getDatabase(message.guild.id); + if (!db.config.logger || !db.config.logger.logDeletes || !db.config.logger.deletesChannel) return; + if (db.config.logger.ignoreChar && message.content.startsWith(db.config.logger.ignoreChar) && db.config.logger.ignoreChan && db.config.logger.ignoreChan.includes(message.channel.id)) return; + + let entry; + let user; + if (message.guild.me.hasPermission("VIEW_AUDIT_LOG")) { + entry = await message.guild.fetchAuditLogs({type: "MESSAGE_DELETE"}).then(audit => audit.entries.first()); + if (entry && entry.createdTimestamp > (Date.now() - 5000)) user = utilities.parseUserId(entry.executor.id); + } + let attachmentNum = 0; + const attachments = []; + for (const attachment of message.attachments) { + attachments.push(attachment[1].url); + attachmentNum += 1; + } + let descText = "A message was deleted."; + const embed = { + color: message.guild.members.cache.get(client.user.id).displayColor, + timestamp: new Date(), + fields: [ + {name: "Author", value: `${message.author}`, inline: true}, + {name: "Channel", value: `${message.channel}`, inline: true}, + {name: "Message", value: `${message.content || "(none)"}`, inline: true}, + ], + footer: { + icon_url: client.user.avatarURL(), + text: "moodE", + }, + }; + if (attachmentNum > 0) embed.fields.push({name: "Attachments", value: `${attachmentNum}`, inline: true}); + if (user) descText = `A message was deleted by ${user.tag}.`; + client.channels.cache.get(db.config.logger.deletesChannel).send(descText, {embed: embed}); + if (attachmentNum > 0) client.channels.cache.get(db.config.logger.deletesChannel).send("Attachments:", {files: attachments}); + }*/ + const message = messages[0]; const db = Storage.getDatabase(message.guild.id); if (!db.config.logger || !db.config.logger.logDeletes || !db.config.logger.deletesChannel) return; + if (db.config.logger.ignoreChar && message.content.startsWith(db.config.logger.ignoreChar) && db.config.logger.ignoreChan && db.config.logger.ignoreChan.includes(message.channel.id)) return; + let entry; let user; if (message.guild.me.hasPermission("VIEW_AUDIT_LOG")) { entry = await message.guild.fetchAuditLogs({type: "MESSAGE_DELETE"}).then(audit => audit.entries.first()); - if (entry.createdTimestamp > (Date.now() - 5000)) user = utilities.parseUserId(entry.executor.id); + if (entry && entry.createdTimestamp > (Date.now() - 5000)) user = utilities.parseUserId(entry.executor.id); + } + let descText = "Several messages were deleted."; + const embed = { + color: message.guild.members.cache.get(client.user.id).displayColor, + timestamp: new Date(), + fields: [ + {name: "Number", value: messages.size, inline: true}, + {name: "Channel", value: `${message.channel}`, inline: true}, + ], + footer: { + icon_url: client.user.avatarURL(), + text: "moodE", + }, + }; + if (user) descText = `Several messages were deleted by ${user.tag}.`; + client.channels.cache.get(db.config.logger.deletesChannel).send(descText, {embed: embed}); +}); + +client.on("messageDelete", async (message) => { + if (!listen) return; + + if (message.partial) { + try { + message = await message.fetch(); + } catch (e) { + return console.log(`${Tools.discordText()}Unable to retreive a deleted message! ${(`(ID: ${message.id})`).grey}`); + } } + + const db = Storage.getDatabase(message.guild.id); + if (!db.config.logger || !db.config.logger.logDeletes || !db.config.logger.deletesChannel) return; if (db.config.logger.ignoreChar && message.content.startsWith(db.config.logger.ignoreChar) && db.config.logger.ignoreChan && db.config.logger.ignoreChan.includes(message.channel.id)) return; + + let entry; + let user; + if (message.guild.me.hasPermission("VIEW_AUDIT_LOG")) { + entry = await message.guild.fetchAuditLogs({type: "MESSAGE_DELETE"}).then(audit => audit.entries.first()); + if (entry && entry.createdTimestamp > (Date.now() - 5000)) user = utilities.parseUserId(entry.executor.id); + } + let attachmentNum = 0; + const attachments = []; + for (const attachment of message.attachments) { + attachments.push(attachment[1].url); + attachmentNum += 1; + } let descText = "A message was deleted."; const embed = { color: message.guild.members.cache.get(client.user.id).displayColor, @@ -84,23 +204,35 @@ client.on("messageDelete", async (message) => { fields: [ {name: "Author", value: `${message.author}`, inline: true}, {name: "Channel", value: `${message.channel}`, inline: true}, - {name: "Message", value: `${message.content || "{embed}"}`, inline: true}, + {name: "Message", value: `${message.content || "(none)"}`, inline: true}, ], footer: { icon_url: client.user.avatarURL(), text: "moodE", }, }; + if (attachmentNum > 0) embed.fields.push({name: "Attachments", value: `${attachmentNum}`, inline: true}); if (user) descText = `A message was deleted by ${user.tag}.`; - client.channels.cache.get(db.config.logger.deletesChannel).send(descText, {embed}); + client.channels.cache.get(db.config.logger.deletesChannel)?.send(descText, {embed: embed}); + if (attachmentNum > 0) client.channels.cache.get(db.config.logger.deletesChannel)?.send("Attachments:", {files: attachments}); }); client.on("messageUpdate", async (oldMessage, newMessage) => { if (!listen) return; + if (oldMessage.partial || newMessage.partial) { + oldMessage = await oldMessage.fetch(); + newMessage = await newMessage.fetch(); + } if (newMessage.author.bot) return; // Don't log bot edits + // Run rules on edits + if (oldMessage.channel.type !== "dm") { + discordEditRules.process(oldMessage, newMessage); + } + const db = Storage.getDatabase(oldMessage.guild.id); if (!db.config.logger || !db.config.logger.logEdits || !db.config.logger.editsChannel) return; if (db.config.logger.ignoreChan && db.config.logger.ignoreChan.includes(oldMessage.channel.id)) return; + if (oldMessage.content === newMessage.content) return; const descText = "A message was edited."; const embed = { color: oldMessage.guild.members.cache.get(client.user.id).displayColor, @@ -108,6 +240,7 @@ client.on("messageUpdate", async (oldMessage, newMessage) => { fields: [ {name: "Author", value: `${oldMessage.author}`, inline: true}, {name: "Channel", value: `${oldMessage.channel}`, inline: true}, + {name: "Link", value: `[Click me!](${oldMessage.url})`, inline: true}, {name: "Old Message", value: `${oldMessage.content || "{embed}"}`, inline: true}, {name: "New Message", value: `${newMessage.content || "{embed}"}`, inline: true}, ], @@ -119,36 +252,36 @@ client.on("messageUpdate", async (oldMessage, newMessage) => { client.channels.cache.get(db.config.logger.editsChannel).send(descText, {embed}); }); -// Legacy Support -client.on("raw", async (event) => { +client.on("messageReactionAdd", async (reaction, user) => { if (!listen) return; - if (!["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE"].includes(event.t)) return; - const channel = client.channels.cache.get(event.d.channel_id); - channel.messages.fetch(event.d.message_id).then(msg => { - const user = msg.guild.members.cache.get(event.d.user_id); - - if (msg.author.id === client.user.id) { - const regex = `\\*\\*"(.+)?(?="\\*\\*)`; - const role = msg.content.match(regex)[1]; - - if (user.id !== client.user.id) { - const roleObj = msg.guild.roles.cache.find(r => r.name === role); - const memberObj = msg.guild.members.cache.get(user.id); - - if (event.t === "MESSAGE_REACTION_ADD") { - memberObj.roles.add(roleObj); - console.log(`${roleObj.name} added to ${memberObj.user.username}`); - } else { - memberObj.roles.remove(roleObj); - console.log(`${roleObj.name} removed from ${memberObj.user.username}`); - } - } + + if (reaction.partial) { + try { + await reaction.fetch(); + } catch (e) { + return console.log(`${Tools.discordText()}Unable to retrieve reaction! ${(`(ID: ${reaction.message.id})`).grey}`); } - }); + } + + discordReactionHandler.process(reaction, user, "Add"); +}); + +client.on("messageReactionRemove", async (reaction, user) => { + if (!listen) return; + + if (reaction.partial) { + try { + await reaction.fetch(); + } catch (e) { + return console.log(`${Tools.discordText()}Unable to retrieve reaction! ${(`(ID: ${reaction.message.id})`).grey}`); + } + } + + discordReactionHandler.process(reaction, user, "Remove"); }); client.on("disconnect", (errMsg, code) => { - console.log(`${discordText}Bot disconnected with code ${code} for reason: ${errMsg}`); + console.log(`${Tools.discordText()}Bot disconnected with code ${code} for reason: ${errMsg}`); client.connect(); }); @@ -159,7 +292,7 @@ async function resolveMessage(message) { let args = message.content.slice(cmd.length + 1).split(","); args = args.map(element => element.trim()); - if (cmd === "help") return discordCommandHandler.helpCommand(args, message); + if (cmd === "help") return await discordCommandHandler.createHelpCommand(args, message); discordCommandHandler.executeCommand(cmd, message, args); } diff --git a/discord/commandHandler.js b/discord/commandHandler.js index db1e256..8d2da4e 100644 --- a/discord/commandHandler.js +++ b/discord/commandHandler.js @@ -14,16 +14,25 @@ const FC_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/fc/"); const JOKE_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/joke/"); const MANAGEMENT_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/management/"); const NSFW_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/nsfw/"); +const RNG_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/rng/"); const databaseDirectory = path.resolve(__dirname, "../databases"); +const EMBED_TEMPLATE = { + author: { + name: `${client.user.username} Help`, + icon_url: client.user.avatarURL(), + }, + title: "Page {{{page}}}", +}; + class CommandHandler { constructor() { this.commands = []; } async init(isReload) { - console.log(`${discordText}${isReload ? "Rel" : "L"}oading commands...`); + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading commands...`); await Promise.all([ this.loadDirectory(COMMANDS_DIRECTORY, Commands.DiscordCommand, "Bot", isReload), this.loadDirectory(DEV_COMMANDS_DIRECTORY, Commands.DevCommand, "Dev", isReload), @@ -32,11 +41,12 @@ class CommandHandler { this.loadDirectory(JOKE_COMMANDS_DIRECTORY, Commands.JokeCommand, "Joke", isReload), this.loadDirectory(MANAGEMENT_COMMANDS_DIRECTORY, Commands.ManagementCommand, "Management", isReload), this.loadDirectory(NSFW_COMMANDS_DIRECTORY, Commands.NsfwCommand, "NSFW", isReload), + this.loadDirectory(RNG_COMMANDS_DIRECTORY, Commands.DiscordCommand, "RNG", isReload), ]); } loadDirectory(directory, Command, type, isReload) { - console.log(`${discordText}${isReload ? "Rel" : "L"}oading ${type.cyan} commands...`); + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading ${type.cyan} commands...`); return new Promise((resolve, reject) => { fs.readdir(directory, (err, files) => { if (err) { @@ -48,7 +58,7 @@ class CommandHandler { if (name.endsWith(".js")) { try { name = name.slice(0, -3); // remove extention - const command = new Command(name, require(directory + "/" + name + ".js")); + const command = new Command(name, require(`${directory}/${name}.js`)); this.commands.push(command); fs.readdir(databaseDirectory, (err, dbs) => { for (let id of dbs) { @@ -60,13 +70,13 @@ class CommandHandler { } } }); - if (!(isReload)) console.log(`${discordText}${isReload ? "Rel" : "L"}oaded command ${type === "NSFW" ? (name.charAt(0) + "*****").green : name.green}`); + if (!(isReload)) console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oaded command ${type === "NSFW" ? (`${name.charAt(0)}*****`).green : name.green}`); } catch (e) { - console.log("Discord: ".yellow + "CommandHandler loadDirectory() error: ".brightRed + `${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); + console.log(`${"Discord: ".yellow + "CommandHandler loadDirectory() error: ".brightRed}${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); } } } - console.log(`${discordText}${type.cyan} commands ${isReload ? "rel" : "l"}oaded!`); + console.log(`${Tools.discordText()}${type.cyan} commands ${isReload ? "rel" : "l"}oaded!`); resolve(); } }); @@ -89,33 +99,31 @@ class CommandHandler { if (command.trigger(cmd)) { const permissions = message.channel.type !== "dm" ? utilities.checkPermissions(message, command.name) : undefined; if (permissions) { - if (command.isNSFW && (permissions.config.nsfw.allowNSFW === false || !(permissions.config.nsfw.nsfwChannels.includes(message.channel.id)))) return message.author.send(`${failureEmoji} NSFW commands are not available in this ${permissions.config.nsfw.allowNSFW ? "channel" : "server"}.`); + if (command.isNSFW && (permissions.config.nsfw.allowNSFW === false || !(permissions.config.nsfw.nsfwChannels.includes(message.channel.id)))) return message.author.send(`${discordFailureEmoji} NSFW commands are not available in this ${permissions.config.nsfw.allowNSFW ? "channel" : "server"}.`); // Admins should skip permission checks if (!isAdmin(message.author.id)) { - if (!command.override && (permissions.config.requiredRoles.length > 0 && !(message.member.roles.cache.some(r => permissions.config.requiredRoles.includes(r.id)))) || (permissions.config.commands[command.name].requiredRoles.length > 0 && !(message.member.roles.cache.some(r => permissions.config.commands[command.name].requiredRoles.includes(r.id))))) return message.author.send(`${failureEmoji} You don't have the required roles to use that command.`); - if (!command.override && permissions.config.bannedChannels.includes(message.channel.id)) return message.author.send(`${failureEmoji} Commands are not permitted in that channel!`); - if (!command.override && permissions.config.bannedUsers.includes(message.author.id)) return message.author.send(`${failureEmoji} You are not permitted to use bot commands in ${message.guild.name}.`); - if (!command.override && permissions.config.commands[command.name].bannedChannels.includes(message.channel.id)) return message.author.send(`${failureEmoji} Commands are not permitted in that channel!`); - if (!command.override && permissions.config.commands[command.name].bannedUsers.includes(message.author.id)) return message.author.send(`${failureEmoji} You are not permitted to the \`\`${discordConfig.commandCharacter}${command.name}\`\` command in ${message.guild.name}.`); - if ((permissions.config.commands[command.name].isElevated || command.elevated) && (!isAdmin(message.author.id) && !isElevated(message.author.id) && !permissions.config.botRanks.manager.includes(message.author.id) && !permissions.config.botRanks.elevated.includes(message.author.id))) return message.author.send(`${failureEmoji} You lack the required permissions to use \`\`${discordConfig.commandCharacter}${command.name}\`\` in ${message.guild.name}.`); - if ((permissions.config.commands[command.name].isManager || command.manager) && (!isAdmin(message.author.id) && !permissions.config.botRanks.manager.includes(message.author.id))) return message.author.send(`${failureEmoji} You lack the required permissions to use \`\`${discordConfig.commandCharacter}${command.name}\`\` in ${message.guild.name}.`); + if (!command.override && (permissions.config.requiredRoles.length > 0 && !(message.member.roles.cache.some(r => permissions.config.requiredRoles.includes(r.id)))) || (permissions.config.commands[command.name].requiredRoles.length > 0 && !(message.member.roles.cache.some(r => permissions.config.commands[command.name].requiredRoles.includes(r.id))))) return message.author.send(`${discordFailureEmoji} You don't have the required roles to use that command.`); + if (!command.override && permissions.config.bannedChannels.includes(message.channel.id)) return message.author.send(`${discordFailureEmoji} Commands are not permitted in that channel!`); + if (!command.override && permissions.config.bannedUsers.includes(message.author.id)) return message.author.send(`${discordFailureEmoji} You are not permitted to use bot commands in ${message.guild.name}.`); + if (!command.override && permissions.config.commands[command.name].bannedChannels.includes(message.channel.id)) return message.author.send(`${discordFailureEmoji} Commands are not permitted in that channel!`); + if (!command.override && permissions.config.commands[command.name].bannedUsers.includes(message.author.id)) return message.author.send(`${discordFailureEmoji} You are not permitted to the \`\`${discordConfig.commandCharacter}${command.name}\`\` command in ${message.guild.name}.`); + if ((permissions.config.commands[command.name].isElevated || command.elevated) && (!isAdmin(message.author.id) && !isElevated(message.author.id) && !permissions.config.botRanks.manager.includes(message.author.id) && !permissions.config.botRanks.elevated.includes(message.author.id) && !message.member.hasPermission("ADMINISTRATOR"))) return message.author.send(`${discordFailureEmoji} You lack the required permissions to use \`\`${discordConfig.commandCharacter}${command.name}\`\` in ${message.guild.name}.`); + if ((permissions.config.commands[command.name].isManager || command.manager) && (!isAdmin(message.author.id) && !permissions.config.botRanks.manager.includes(message.author.id) && !message.member.hasPermission("ADMINISTRATOR"))) return message.author.send(`${discordFailureEmoji} You lack the required permissions to use \`\`${discordConfig.commandCharacter}${command.name}\`\` in ${message.guild.name}.`); } } if (command.adminOnly && !isAdmin(message.author.id)) { - return message.channel.send(`${failureEmoji} You do not have permission to do that!`); - } - if (command.elevated && !isAdmin(message.author.id) && !isElevated(message.author.id)) { - return message.channel.send(`${failureEmoji} You do not have permission to do that!`); + return message.channel.send(`${discordFailureEmoji} You do not have permission to do that!`); } + if (command.noPm && message.channel.type === "dm") { - return message.channel.send(`${failureEmoji} This command is not available in PMs!`); + return message.channel.send(`${discordFailureEmoji} This command is not available in PMs!`); } if (command.commandType === "DexCommand") { for (let i = 0; i < args.length; i++) { - if (["lgpe", "gen7", "gen6", "gen5", "gen4", "gen3", "gen2", "gen1", "usum", "sm", "oras", "xy", "bw2", "bw", "hgss", "dppt", "adv", "rse", "frlg", "gsc", "rby"].includes(Tools.toId(args[i]))) { + if (["gen8", "swsh", "lgpe", "gen7", "gen6", "gen5", "gen4", "gen3", "gen2", "gen1", "usum", "sm", "oras", "xy", "bw2", "bw", "hgss", "dppt", "adv", "rse", "frlg", "gsc", "rby"].includes(Tools.toId(args[i]))) { switch (Tools.toId(args[i])) { case "lgpe": passDex = Dex.mod("lgpe"); @@ -155,8 +163,6 @@ class CommandHandler { case "rbyg": passDex = Dex.mod("gen1"); break; - default: - passDex = Dex.mod("gen7"); } args.splice(i, 1); break; @@ -165,7 +171,7 @@ class CommandHandler { } const hrStart = process.hrtime(); - console.log(`${discordText}Executing command: ${command.name.cyan}`); + console.log(`${Tools.discordText()}Executing command: ${command.name.cyan}`); let commandOutput; try { if (command.commandType === "DexCommand") { @@ -175,7 +181,7 @@ class CommandHandler { } } catch (e) { if (discordConfig.logChannel) { - message.channel.send(`${discordConfig.failureEmoji} The command crashed! The crash has been logged and a fix will (hopefully) be on its way soon.`); + message.channel.send(`${discordFailureEmoji} The command crashed! The crash has been logged and a fix will (hopefully) be on its way soon.`); const embed = { timestamp: new Date(), fields: [ @@ -183,7 +189,7 @@ class CommandHandler { {name: "Command", value: `${command.name}`}, {name: "Input", value: cmd}, {name: "args", value: JSON.stringify(args)}, - {name: "Server", value: message.channel.type === "dm" ? "Private Message" : message.guild.name + " (" + message.guild.id + ")"}, + {name: "Server", value: message.channel.type === "dm" ? "Private Message" : `${message.guild.name} (${message.guild.id})`}, {name: "User", value: `${authorId} (${message.author.username}#${message.author.discriminator})`}, ], footer: { @@ -192,102 +198,230 @@ class CommandHandler { }, }; client.channels.cache.get(discordConfig.logChannel).send("A command crashed! Stack trace:", {embed}); - client.channels.cache.get(discordConfig.logChannel).send("```" + e.stack + "```"); + client.channels.cache.get(discordConfig.logChannel).send(`\`\`\`${e.stack}\`\`\``); } else { - message.channel.send(`${discordConfig.failureEmoji} The command crashed! Please notify the bot owner (and include the output below), or try again later.\n\`\`\`${e}\`\`\``); + message.channel.send(`${discordFailureEmoji} The command crashed! Please notify the bot owner (and include the output below), or try again later.\n\`\`\`${e}\`\`\``); } - console.log(`${discordText}\n${"ERROR".brightRed}: ${e} at ${e.stack}\nfrom command ${command.name}\nwith input ${cmd}\nwith args ${JSON.stringify(args)}\nin ${message.channel.type === "dm" ? "a private message with" : "server " + message.guild.name + " (" + message.guild.id + ") by"} user ${authorId} (${message.author.username}#${message.author.discriminator})`); + console.log(`${Tools.discordText()}\n${"ERROR".brightRed}: ${e} at ${e.stack}\nfrom command ${command.name}\nwith input ${cmd}\nwith args ${JSON.stringify(args)}\nin ${message.channel.type === "dm" ? "a private message with" : `server ${message.guild.name} (${message.guild.id}) by`} user ${authorId} (${message.author.username}#${message.author.discriminator})`); commandOutput = false; } if (commandOutput || command.hasCustomFormatting) { const hrEnd = process.hrtime(hrStart); const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; - console.log(`${discordText}Executed command: ${command.name.green} in ${timeString}`); + console.log(`${Tools.discordText()}Executed command: ${command.name.green} in ${timeString}`); } else { - console.log(`${discordText}Error parsing command: ${command.name.brightRed} - command function does not return anything!`); + console.log(`${Tools.discordText()}Error parsing command: ${command.name.brightRed} - command function does not return anything!`); } } } } - helpCommand(cmd, message) { + async createHelpCommand(cmd, message) { const hrStart = process.hrtime(); - console.log(`${discordText}Executing command: ${"help".cyan}`); - const botCommands = []; - const devCommands = []; - const dexCommands = []; - const fcCommands = []; - const managementCommands = []; - let sendMsg = []; - if (!(cmd[0])) { - message.author.send(`List of commands; use \`${discordConfig.commandCharacter}help \` for more information:`); - for (let i = 0; i < this.commands.length; i++) { - const command = this.commands[i]; - if (!command.disabled && !command.isNSFW && command.commandType !== "JokeCommand") { - const cmdText = `${discordConfig.commandCharacter}${command.name}${command.desc ? " - " + command.desc : ""}`; - switch (command.commandType) { - case "BotCommand": - botCommands.push(cmdText); - break; - case "DevCommand": - devCommands.push(cmdText); - break; - case "DexCommand": - dexCommands.push(cmdText); - break; - case "FcCommand": - fcCommands.push(cmdText); - break; - case "ManagementCommand": - managementCommands.push(cmdText); - break; - } + console.log(`${Tools.discordText()}Executing command: ${"help".cyan}`); + const target = message.guild && message.guild.members.cache.get(client.user.id).hasPermission("ADD_REACTIONS") ? message.channel : message.author; + if (cmd[0]) { + // Specific Help Command + let c; + for (const command of this.commands) { + if ([command.name, ...command.aliases].includes(Tools.toId(cmd[0]))) { + c = command; + break; } } - const hrEnd = process.hrtime(hrStart); - const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; - console.log(`${discordText}Executed command: ${"help".green} in ${timeString}`); - if (botCommands.length > 0) message.author.send(`Bot Commands:\n\`\`\`${botCommands.join("\n")}\`\`\``); - if (devCommands.length > 0) message.author.send(`Dev Commands:\n\`\`\`${devCommands.join("\n")}\`\`\``); - if (dexCommands.length > 0) message.author.send(`Dex Commands:\n\`\`\`${dexCommands.join("\n")}\`\`\``); - if (fcCommands.length > 0) message.author.send(`FC Commands:\n\`\`\`${fcCommands.join("\n")}\`\`\``); - if (managementCommands.length > 0) message.author.send(`Management Commands:\n\`\`\`${managementCommands.join("\n")}\`\`\``); - return true; + if (!c) { + target.send(`${discordFailureEmoji} No command "${cmd[0]}" found!`); + } else { + let db; + if (message.guild) { + db = Storage.getDatabase(message.guild.id).config.commands[c.name]; + } + + const embed = Object.assign({}, EMBED_TEMPLATE); + embed.title = `${discordConfig.commandCharacter}${c.name}`; + embed.description = `**About**: ${c.longDesc || c.desc || "No info available."}`; + embed.fields = [ + { + name: "Usage:", + value: `${discordConfig.commandCharacter}${c.name} ${c.usage || ""}`, + }, + { + name: "isElevated", + value: db ? db.isElevated : c.elevated, + inline: true, + }, + { + name: "isManager", + value: db ? db.isManager : c.manager, + inline: true, + }, + ]; + if (c.aliases.length > 0) { + embed.fields.push({ + name: "Aliases", + value: c.aliases.join(", "), + }); + } + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.discordText()}Executed command: ${"help".green} in ${timeString}`); + return target.send({embed}); + } } - const lookup = Tools.toId(cmd[0]); - let command; - let matched = false; + // Generic Help Command + const embeds = await this.buildPages(message, message.author, 1); + const embed = embeds[0]; + + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.discordText()}Executed command: ${"help".green} in ${timeString}`); + + const msg = await target.send(`moodE Help (for ${message.author}): Page 1 of ${embeds.length}`, {embed}); + await msg.react("\u{23EA}"); // ⏪ Rewind Emoji + await msg.react("\u{25C0}"); // ◀️ Back Emoji + await msg.react("\u{25B6}"); // ▶️ Play Emoji + await msg.react("\u{23E9}"); // ⏩ Fast Forward Emoji + await msg.react("\u{1F522}"); // 🔢 1234 Emoji + await msg.react("\u{1F4D8}"); // 📘 Blue Book Emoji + } + + async changeHelpPage(message, user, page, maxPage) { + const embeds = await this.buildPages(message, user, maxPage); + const embed = embeds[page - 1]; + await message.edit(`moodE Help (for ${user}): Page ${page} of ${embeds.length}`, {embed}); + } + + // Returns an array of embeds + async buildPages(message, user) { + const pages = []; + // Page 1 should always be the welcome page + const pageOne = Object.assign({}, EMBED_TEMPLATE); + pageOne.description = "Hello! Welcome to the help page."; + pageOne.title = pageOne.title.replace("{{{page}}}", "1 - Welcome"); + pageOne.fields = [ + { + name: "How does this work?", + value: `I'm glad you asked! You can control the help page using the reactions below. + \u{23EA} takes you back to the first page (this one!) + \u{25C0} takes you to the previous page + \u{25B6} takes you to the next page + \u{23E9} takes you to the last page + \u{1F522} lets you type a page number to go to + \u{1F4D8} takes you to the contents page + + Don't worry! This command can only be controlled by reactions from the user. + Pages contain information about how to use the bot, and its commands. + Click \u{25B6} to get started!`.replace(/\t/g, ""), + }, + ]; + pages.push(pageOne); + + // Populate contents page and get a nice list of commands that we can use for the rest of the pages + const commands = { + "Bot Commands": { + desc: "Core, non-specific bot commands.", + perPage: 4, + }, + "Dev Commands": { + desc: "Commands for bot developers.", + perPage: 4, + }, + "Dex Commands": { + desc: "Pokedex commands.", + perPage: 4, + }, + "Fc Commands": { + desc: "Commands for accessing and using the Friend Code database.", + perPage: 4, + }, + "Management Commands": { + desc: "Commands for server, bot, and user management.", + perPage: 4, + }, + "Nsfw Commands": { + desc: "Commands that are Not Safe For Work.", + perPage: 4, + }, + }; for (let i = 0; i < this.commands.length; i++) { - if (matched) break; - command = this.commands[i]; - if (command.name === lookup || (command.aliases && command.aliases.includes(lookup))) matched = true; + const command = this.commands[i]; + const type = `${command.commandType.split("Command")[0]} Commands`; + if (!commands[type]) commands[type] = {}; + if (!commands[type].commandList) commands[type].commandList = {}; + commands[type].commandList[command.name] = command; } - if (!matched) return message.author.send(`${failureEmoji} No command "${lookup}" found!`); + const pageTwo = Object.assign({}, EMBED_TEMPLATE); + pageTwo.title = pageTwo.title.replace("{{{page}}}", "2 - Contents & How-To"); + pageTwo.description = "This page goes over how to use the bot, and acts as a contents page for the Help command."; + pageTwo.fields = [ + { + name: "How-To", + value: `moodE commands are prefixed with \`${discordConfig.commandCharacter}\`. It will attempt to parse any message starting with this character as a command. + In the following pages is a list of all of moodE's commands, and for specific, more detailed help with a command, you can use \`${discordConfig.commandCharacter}help \` (without the brackets!)`.replace(/\t/g, ""), + }, + { + name: "Contents", + value: `React with \u{1F522} below to quickly jump to one of these pages!`, + }, + ]; - if (command.adminOnly && !isAdmin(message.author.id)) { - return "```" + `${command.name} is an admin-only command.` + "```"; + const skipTypes = ["Joke Commands", "Nsfw Commands"]; + if (message.channel.type !== "dm") { + const db = Storage.getDatabase(message.guild.id); + if (message.channel.nsfw || (db.config.nsfw && db.config.nsfw.allowNSFW && db.config.nsfw.nsfwChannels.includes(message.channel.id))) { + skipTypes.splice(1, 1); + } } - if (command.elevated && !isElevated(message.author.id)) { - return "```" + `${command.name} is an elevated-only command.` + "```"; + + let lastPage = 3; + for (const key of Object.keys(commands)) { + if (skipTypes.includes(key)) continue; + let count = 0; + const field = { + name: `${key} - Page {{{page}}}`, + value: "", + }; + const cmdList = [`${commands[key].desc} List:`]; + for (const command of Object.values(commands[key].commandList)) { + if (command.disabled) continue; + count++; + cmdList.push(command.name); + } + commands[key].count = count; + field.name = field.name.replace("{{{page}}}", lastPage); + lastPage += Math.ceil(count / commands[key].perPage); + field.value = cmdList.join(", ").replace(", ,", "").replace(" , ", " "); + pageTwo.fields.push(field); } + pages.push(pageTwo); - sendMsg = [ - command.name, - `${discordConfig.commandCharacter}${command.name}${command.usage.length > 0 ? " " + command.usage : ""}`, - command.longDesc, - "", - `${command.aliases.length > 0 ? "Aliases: " + command.aliases.join(", ") : ""}`, - ]; - for (let i = 0; i < command.options.length; i++) { - sendMsg.push(` ${command.options[i].toString()}: ${command.options[i].desc}`); + let pageCounter = 3; + for (const key of Object.keys(commands)) { + if (skipTypes.includes(key)) continue; + const commandList = Object.values(commands[key].commandList); + let p = Object.assign({}, EMBED_TEMPLATE); + p.fields = []; + let j = 1; + let hasSetTitle = false; + for (const command of commandList) { + if (command.disabled) continue; + p.title = `Page ${pageCounter} - ${key}${!hasSetTitle ? "" : " (Cont.)"}`; + p.fields.push({ + name: `${discordConfig.commandCharacter}${command.name}`, + value: `Usage: ${discordConfig.commandCharacter}${command.name} ${command.usage || ""}\n${command.desc}`, + }); + if (j % 4 === 0 || j === commandList.length) { + pageCounter++; + pages.push(p); + hasSetTitle = true; + p = Object.assign({}, EMBED_TEMPLATE); + p.fields = []; + } + j++; + } } - sendMsg = sendMsg.join("\n"); - sendMsg = "```" + sendMsg + "```"; - const hrEnd = process.hrtime(hrStart); - const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; - console.log(`${discordText}Executed command: ${"help".green} in ${timeString}`); - return message.channel.send(sendMsg); + return pages; } } diff --git a/discord/commands.js b/discord/commands.js index 9feda13..3eaf430 100644 --- a/discord/commands.js +++ b/discord/commands.js @@ -7,8 +7,8 @@ class Command { this.desc = cmd.desc || "No description."; this.longDesc = cmd.longDesc || this.desc; this.adminOnly = cmd.adminOnly || false; - this.manager = cmd.manager || false; - this.elevated = cmd.elevated || false; + this.manager = cmd.isManager || false; + this.elevated = cmd.isElevated || false; this.aliases = cmd.aliases || []; this.disabled = cmd.disabled || false; this.hasCustomFormatting = cmd.hasCustomFormatting || false; diff --git a/discord/commands/8ball.js b/discord/commands/8ball.js new file mode 100644 index 0000000..c5fae36 --- /dev/null +++ b/discord/commands/8ball.js @@ -0,0 +1,33 @@ +"use strict"; + +const phrases = [ + "As I see it, yes.", + "Ask again later.", + "Better not tell you now.", + "Cannot predict now.", + "Concentrate and ask again.", + "Don't count on it.", + "It is certain.", + "It is decidedly so.", + "Most likely.", + "My reply is no.", + "My sources say no.", + "Outlook not so good.", + "Outlook good.", + "Reply hazy, try again.", + "Signs point to yes.", + "Very doubtful.", + "Without a doubt.", + "Yes.", + "Yes – definitely.", + "You may rely on it.", +]; + +module.exports = { + desc: "Asks the magic 8-Ball a question.", + usage: "", + aliases: ["8"], + async process(message, args) { + return message.channel.send(`:8ball: ${Tools.sampleOne(phrases)}`); + }, +}; diff --git a/discord/commands/about.js b/discord/commands/about.js index 951fa88..a0a8d38 100644 --- a/discord/commands/about.js +++ b/discord/commands/about.js @@ -16,11 +16,11 @@ module.exports = { }, color: message.channel.type === "dm" ? 0 : message.guild.members.cache.get(client.user.id).displayColor, fields: [ - {name: "Invite Link", value: "[Click here](https://discordapp.com/oauth2/authorize?&client_id=537773790445305866&scope=bot&permissions=8)!"}, + {name: "Invite Link", value: "[Click here](https://discordapp.com/oauth2/authorize?&client_id=537773790445305866&scope=bot&permissions=403041478)!"}, {name: "Language", value: "JavaScript (discordjs)"}, ], footer: { - text: `moodE version ${packagejson.version}`, + text: `moodE version ${packagejson.version} @ ${hash}`, }, }}); }, diff --git a/discord/commands/bird.js b/discord/commands/bird.js new file mode 100644 index 0000000..d1f3e83 --- /dev/null +++ b/discord/commands/bird.js @@ -0,0 +1,40 @@ +"use strict"; + +const https = require("https"); + +module.exports = { + desc: "Gets a random bird photo.", + hasCustomFormatting: true, + aliases: ["randombird", "randbird", "birb", "randombirb", "randbirb"], + async process(message, args) { + const options = { + hostname: "shibe.online", + path: "/api/birds?count=1&urls=true&httpsUrls=true", + method: "GET", + }; + let send = true; + const bird = new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + res.on("data", (res) => { + resolve(res); + }); + }); + + req.on("error", (err) => { + send = false; + resolve(err); + }); + req.end(); + }); + const data = await bird; + const embed = { + image: { + url: JSON.parse(data.toString())[0], + }, + footer: { + text: JSON.parse(data.toString())[0], + }, + }; + return send ? message.channel.send({embed}) : message.channel.send("Something went wrong..."); + }, +}; diff --git a/discord/commands/color.js b/discord/commands/color.js new file mode 100644 index 0000000..37e18e0 --- /dev/null +++ b/discord/commands/color.js @@ -0,0 +1,94 @@ +"use strict"; + +const nodeHtmlToImage = require("node-html-to-image"); + +module.exports = { + desc: "Displays the color of a provided hex code.", + usage: "", + aliases: ["colour"], + async process(message, args) { + const hexRegex = /^(0[xX]){1}[A-Fa-f0-9]{6}$|^#[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{6}$/; + let color = args[0]; + if (color.trim().length > 8) { + color = color.trim().substr(0, 8); + } + color = color.replace("0x", "#"); + if (!color.startsWith("#")) color = `#${color}`; + color = color.toUpperCase(); + if (!(hexRegex.test(color.trim()))) return message.channel.send(`${discordFailureEmoji} Unable to coerce "${args[0]}" as a hex code!`); + color = hexRegex.exec(color)[0]; + message.channel.startTyping(); + const image = await nodeHtmlToImage({ + // Hardcode `executablePath: "chromium-browser"` - Remove this if not running on Raspberry Pi! + puppeteerArgs: {executablePath: "chromium-browser", args: ["--no-sandbox", "--disable-setuid-sandbox"]}, + html: ` + + + + + + +
${color}
+
Dark Mode
+
Light Mode
+ + + +`, + }); + message.channel.stopTyping(); + return message.channel.send({files: [image]}); + }, +}; diff --git a/discord/commands/convert.js b/discord/commands/convert.js new file mode 100644 index 0000000..805d4ce --- /dev/null +++ b/discord/commands/convert.js @@ -0,0 +1,26 @@ +"use strict"; + +const convert = require("convert-units"); + +module.exports = { + desc: "Converts units.", + longDesc: "Converts from one unit to another. Valid units are: mm, cm, m, km, in, yd, ft-us, ft, mi, mm2, cm2, m2, ha, km2, in2, yd2, ft2, ac, mi2, mcg, mg, g, kg, mt, oz, lb, t, mm3, cm3, ml, cl, dl, l, kl, m3, km3, krm, tsk, msk, kkp, glas, kanna, tsp, Tbs, in3, fl-oz, cup, pnt, qt, gal, ft3, yd3, ea, dz, C, K, F, R, ns, mu, ms, s, min, h, d, week, month, year, b, Kb, Mb, Gb, Tb, B, KB, MB, GB, TB, ppm, ppb, ppt, ppq, m/s, km/h, m/h, knot, ft/s, min/km, s/m, min/mi, s/ft, Pa, kPa, MPa, hPa, bar, torr, psi, ksi, A, mA, kA, V, mV, kV, W, mW, kW, MW, GW, VAR, mVAR, kVAR, MVAR, GVAR, VA, mVA, kVA, MVA, GVA, Wh, mWh, kWh, MWh, GWh, J, kJ, VARh, mVARh, kVARh, MVARh, GVARh, mm3/s, cm3/s, ml/s, cl/s, dl/s, l/s, l/min, l/h, kl/s, kl/min, kl/h, m3/s, m3/min, m3/h, km3/s, tsp/s, Tbs/s, in3/s, in3/min, in3/h, fl-oz/s, fl-oz/min, fl-oz/h, cup/s, pnt/s, pnt/min, pnt/h, qt/s, gal/s, gal/min, gal/h, ft3/s, ft3/min, ft3/h, yd3/s, yd3/min, yd3/h, lx, ft-cd, mHz, Hz, kHz, MHz, GHz, THz, rpm, deg/s, rad/s, rad, deg, grad, arcmin, arcsec", + usage: ", \ne.g. 70km, mi\n", + aliases: ["converter", "conv"], + async process(message, args) { + if (!args[1]) return message.channel.send(`${discordFailureEmoji} Expected a unit to convert to!`); + try { + let i = 0; + for (i; i < args[0].length; i++) { + if (isNaN(parseInt(args[0][i])) && args[0][i] !== "-") break; + } + const measure = args[0].trim().substring(0, i).trim() || 0; + const unit = args[0].trim().substring(i, args[0].trim().length).trim(); + if (!unit) return message.channel.send(`${discordFailureEmoji} Expected a unit to convert from!`); + const conv = convert(measure).from(unit).to(args[1].trim()); + return message.channel.send(`${measure}${unit} = ${conv}${args[1].trim()}`); + } catch (e) { + message.channel.send(`${discordFailureEmoji} ${e.stack.split("\n")[0]}`); + } + }, +}; diff --git a/discord/commands/dev/eval.js b/discord/commands/dev/eval.js index 33a3b75..1ce7497 100644 --- a/discord/commands/dev/eval.js +++ b/discord/commands/dev/eval.js @@ -1,6 +1,9 @@ "use strict"; +const LCRNG = require("../../../sources/rng/lcrng.js"); // eslint-disable-line const utilities = require("../../utilities.js"); // eslint-disable-line +const homoglyph = require("../../../sources/homoglyph.js"); // eslint-disable-line +const removeDiacritics = require("diacritics").remove; // eslint-disable-line module.exports = { desc: "Evaluates arbitrary javascript.", @@ -12,12 +15,10 @@ module.exports = { let output; try { output = eval(args); - if (output.constructor === {}.constructor) { - output = JSON.stringify(output, null, 2); - } + output = JSON.stringify(output, null, 2); } catch (e) { return message.channel.send(`Error while evaluating expression: ${e}`); } - return message.channel.send(output); + return message.channel.send(`\`\`\`${output}\`\`\``); }, }; diff --git a/discord/commands/dev/hotpatch.js b/discord/commands/dev/hotpatch.js index 6d9bf4d..0daed11 100644 --- a/discord/commands/dev/hotpatch.js +++ b/discord/commands/dev/hotpatch.js @@ -8,9 +8,43 @@ module.exports = { async process(message, args) { let silent; if (args[0]) silent = true; - console.log(`${discordText}Hot-patching ${silent ? "(silently)".grey : ""}...`); - console.log(`${discordText}--------------`); + console.log(`${Tools.discordText()}Hot-patching ${silent ? "(silently)".grey : ""}...`); + console.log(`${Tools.discordText()}--------------`); + + for (const plugin of DiscordPlugins) { + if (typeof plugin.onEnd === "function") { + console.log(`${Tools.discordText()}Unloading ${plugin.name.cyan} module...`); + plugin.onEnd(); + } + } + Tools.uncacheDir("discord/"); + Tools.uncacheDir("sources/"); + + global.Storage = require("../../../sources/storage.js"); + Storage.importDatabases(); + global.Tools = require("../../../sources/tools.js"); + + // From https://github.com/sirDonovan/Cassius/blob/master/app.js#L46 + let pluginsList; + const plugins = fs.readdirSync(path.resolve(`${__dirname}/../../plugins`)); + for (let i = 0, len = plugins.length; i < len; i++) { + const fileName = plugins[i]; + if (!fileName.endsWith(".js")) continue; + if (!pluginsList) pluginsList = []; + const file = require(`../../plugins/${fileName}`); + if (file.name && !file.disabled) { + global[file.name] = file; + if (typeof file.onLoad === "function") file.onLoad(); + } + pluginsList.push(file); + } + + global.DiscordPlugins = pluginsList; + + global.DiscordEditRules = require("../../editRules.js"); + global.discordEditRules = new DiscordEditRules(); + await discordEditRules.init(true); global.DiscordMessageParser = require("../../messageParser.js"); global.discordMessageParser = new DiscordMessageParser(); @@ -20,6 +54,10 @@ module.exports = { global.discordCommandHandler = new DiscordCommandHandler(); discordCommandHandler.init(true); + global.DiscordReactionHandler = require("../../reactionHandler.js"); + global.discordReactionHandler = new DiscordReactionHandler(); + await discordReactionHandler.init(true); + if (!silent) { return message.channel.send("Hotpatch completed!"); } else { diff --git a/discord/commands/dev/ip.js b/discord/commands/dev/ip.js new file mode 100644 index 0000000..ccedf44 --- /dev/null +++ b/discord/commands/dev/ip.js @@ -0,0 +1,15 @@ +"use strict"; + +const child_process = require("child_process"); +const util = require("util"); + +const exec = util.promisify(child_process.exec); + +module.exports = { + desc: "Returns the bot's public IP address.", + adminOnly: true, + async process(message, args) { + const ip = await exec("curl ifconfig.me").catch(e => console.log(e)); + return message.channel.send(ip.stdout); + }, +}; diff --git a/discord/commands/dev/kill.js b/discord/commands/dev/kill.js index 072ce4a..1976edc 100644 --- a/discord/commands/dev/kill.js +++ b/discord/commands/dev/kill.js @@ -5,8 +5,8 @@ module.exports = { adminOnly: true, async process(message, args) { await message.channel.send("Goodbye!"); - console.log(`${discordText}${"Ending all bot processes...".brightRed}`); - console.log(`${discordText}${"This can now be safely closed!".green}`); + console.log(`${Tools.discordText()}${"Ending all bot processes...".brightRed}`); + console.log(`${Tools.discordText()}${"This can now be safely closed!".green}`); eval("process.exit(0);"); }, }; diff --git a/discord/commands/dev/pull.js b/discord/commands/dev/pull.js index 92c1a8e..18bf836 100644 --- a/discord/commands/dev/pull.js +++ b/discord/commands/dev/pull.js @@ -10,28 +10,30 @@ module.exports = { aliases: ["update", "git"], adminOnly: true, async process(message, args) { - message.channel.send("Attempting git pull..."); + const msg = await message.channel.send("Attempting git pull..."); process.chdir(path.resolve(__dirname, "../../../")); const remoteOutput = await exec("git remote -v").catch(e => console.log(e)); if (!remoteOutput || remoteOutput.Error) { - console.log(`${moodeText}${"Error".red}: No git remote output.`); - return message.channel.send(`${failureEmoji} No git remote output.`); + console.log(`${Tools.moodeText()}${"Error".red}: No git remote output.`); + return msg.edit(`${discordFailureEmoji} No git remote output.`); } - console.log(`${moodeText}Attempting pull...`); + console.log(`${Tools.moodeText()}Attempting pull...`); const pull = await exec("git pull"); if (!pull || pull.Error) { console.log(`${moodeText}Error: could not pull origin.`); - return message.channel.send(`${failureEmoji} Error: could not pull origin.`); + return msg.edit(`${discordFailureEmoji} Error: could not pull origin.`); } if (pull.stdout.replace("\n", "").replace(/-/g, " ") === "Already up to date.") { - console.log(`${moodeText}Already up to date!`); - return message.channel.send(`${failureEmoji} Already up to date!`); + console.log(`${Tools.moodeText()}Already up to date!`); + return msg.edit(`${discordFailureEmoji} Already up to date!`); } else { - console.log(`${moodeText}Pull completed!`); - return message.channel.send(`${successEmoji} Pull completed!`); + const moodeHash = await exec("git rev-parse --short HEAD"); + global.hash = moodeHash.stdout.trim(); + console.log(`${Tools.moodeText()}Pull completed!`); + return msg.edit(`${discordSuccessEmoji} Pull completed!\`\`\`diff\n${pull.stdout}\`\`\``); } }, }; diff --git a/discord/commands/dev/react.js b/discord/commands/dev/react.js new file mode 100644 index 0000000..b49f8b4 --- /dev/null +++ b/discord/commands/dev/react.js @@ -0,0 +1,19 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +module.exports = { + desc: "Makes the react to a message.", + usage: ", ", + adminOnly: true, + async process(message, args) { + if (!args[1]) return; + try { + const msg = await utilities.parseMessageId(message, args[0].trim()); + await msg.react(args[1].trim()); + return message.channel.send(`${discordSuccessEmoji} Reaction added!`); + } catch (e) { + console.log(e.stack); + } + }, +}; diff --git a/discord/commands/dex/hiddenpower.js b/discord/commands/dex/hiddenpower.js index 772ed1a..aa5b876 100644 --- a/discord/commands/dex/hiddenpower.js +++ b/discord/commands/dex/hiddenpower.js @@ -42,9 +42,9 @@ module.exports = { if (ivs.length !== 6) return message.channel.send(`${discordConfig.commandCharacter}hiddenpower ${this.usage}`); for (let i = 0; i < ivs.length; i++) { ivs[i] = Tools.toId(ivs[i]); - if (isNaN(ivs[i])) return message.channel.send(`${discordConfig.failureEmoji} Unable to coerce "${ivs[i]}" as a number!`); + if (isNaN(ivs[i])) return message.channel.send(`${discordFailureEmoji} Unable to coerce "${ivs[i]}" as a number!`); ivs[i] = parseInt(ivs[i]); - if (ivs[i] < 0 || ivs[i] > 31) return message.channel.send(`${discordConfig.failureEmoji} ${ivs[i]} must be a number greater than or equal to 0 and less than or equal to 31!`); + if (ivs[i] < 0 || ivs[i] > 31) return message.channel.send(`${discordFailureEmoji} ${ivs[i]} must be a number greater than or equal to 0 and less than or equal to 31!`); } const hp = ivs[0]; const atk = ivs[1]; diff --git a/discord/commands/dex/item.js b/discord/commands/dex/item.js index 4062364..d433f10 100644 --- a/discord/commands/dex/item.js +++ b/discord/commands/dex/item.js @@ -35,9 +35,9 @@ module.exports = { url: `https://www.smogon.com/dex/${utilities.toSmogonString(dex.gen)}/items/${(item.name.split(" ").join("-")).toLowerCase()}/`, author: { name: item.name, - icon_url: "https://play.pokemonshowdown.com/sprites/itemicons/" + (item.name.split(" ").join("-")).toLowerCase() + ".png", + icon_url: `https://play.pokemonshowdown.com/sprites/itemicons/${(item.name.split(" ").join("-")).toLowerCase()}.png`, }, - color: message.channel.type === "dm" ? 0 : message.guild.members.cache.get(discordConfig.botID).displayColor, + color: message.channel.type === "dm" ? 0 : message.guild.members.cache.get(client.user.id).displayColor, fields: [], footer: { text: `Introduced in Gen ${item.gen}`, diff --git a/discord/commands/dex/learn.js b/discord/commands/dex/learn.js index 879edca..c070b2d 100644 --- a/discord/commands/dex/learn.js +++ b/discord/commands/dex/learn.js @@ -31,7 +31,7 @@ module.exports = { let pokemon = dex.getSpecies(args[0]); const moves = []; if (!pokemon || !pokemon.exists) { - pokemon = dex.dataSearch(args[0], ["Pokedex", "Movedex"]); + pokemon = dex.dataSearch(args[0], ["Pokedex", "Moves"]); if (!pokemon.exists || pokemon[0].searchType === "move") { pokemon = null; if (args.length === 1) { @@ -108,7 +108,6 @@ module.exports = { const Species = dex.getSpecies(allMons[i]); const monset = new Learnset(Species, dex); if (monset.canHaveMove(move.id, dex.gen, true)) { - console.log(dex.data.Pokedex[allMons[i]]); validMons.push(dex.data.Pokedex[allMons[i]].name); } } @@ -121,10 +120,10 @@ module.exports = { if (search.length === 0) { search = learnset.findMove(move.id, null); if (search.length === 0) { - sendMsg.push(`${pokemon.name} cannot obtain ${move.name} in gen${dex.gen}`); + sendMsg.push(`${pokemon.name} cannot learn ${move.name} in gen${dex.gen}`); continue; } else { - sendMsg.push(`${pokemon.name} can obtain ${move.name} in another generation.`); + sendMsg.push(`${pokemon.name} can learn ${move.name} in another generation.`); continue; } } diff --git a/discord/commands/dex/move.js b/discord/commands/dex/move.js index bf99a7f..4359b7d 100644 --- a/discord/commands/dex/move.js +++ b/discord/commands/dex/move.js @@ -7,10 +7,10 @@ const stats = ["atk", "def", "spa", "spd", "spe", "accuracy", "evasion"]; const exceptions_100_fight = ["Low Kick", "Reversal", "Final Gambit"]; const exceptions_80_fight = ["Double Kick", "Triple Kick"]; const exceptions_75_fight = ["Counter", "Seismic Toss"]; -const exceptions_140 = ["Crush Grip", "Wring Out", "Magnitude", "Double Iron Bash"]; -const exceptions_130 = ["Pin Missile", "Power Trip", "Punishment", "Dragon Darts", "Dual Chop", "Electro Ball", "Heat Crash", +const exceptions_140 = ["Triple Axel", "Crush Grip", "Wring Out", "Magnitude", "Double Iron Bash", "Rising Voltage"]; +const exceptions_130 = ["Scale Shot", "Dual Wing Beat", "Terrain Pulse", "Pin Missile", "Power Trip", "Punishment", "Dragon Darts", "Dual Chop", "Electro Ball", "Heat Crash", "Bullet Seed", "Grass Knot", "Bonemerang", "Bone Rush", "Fissure", "Icicle Spear", "Sheer Cold", "Weather Ball", "Tail Slap", "Guillotine", "Horn Drill", - "Flail", "Return", "Frustration", "Endeavor", "Natural Gift", "Trump Card", "Stored Power", "Rock Blast", "Gear Grind", "Gyro Ball", "Heavy Slam", "Bolt Beak (Doubled)", "Fishious Rend (Doubled)"]; + "Flail", "Return", "Frustration", "Endeavor", "Natural Gift", "Trump Card", "Stored Power", "Rock Blast", "Gear Grind", "Gyro Ball", "Heavy Slam"]; const exceptions_120 = ["Double Hit", "Spike Cannon"]; const exceptions_100 = ["Twineedle", "Beat Up", "Fling", "Dragon Rage", "Nature's Madness", "Night Shade", "Comet Punch", "Fury Swipes", "Sonic Boom", "Bide", "Super Fang", "Present", "Spit Up", "Psywave", "Mirror Coat", "Metal Burst"]; @@ -59,7 +59,7 @@ module.exports = { } else { zStr = `(Z: ${move.zMovePower})`; } - zStr = " " + zStr; + zStr = ` ${zStr}`; } let maxPower = ""; @@ -81,7 +81,7 @@ module.exports = { else if (move.basePower >= 45) maxPower = 100; else maxPower = 90; } - maxPower = " (Max Move Power: " + maxPower + ")"; + maxPower = ` (Max Move Power: ${maxPower})`; } let behaviorFlags = ""; @@ -156,7 +156,7 @@ module.exports = { const embed = { title: move.name, - description: `Base Power: ${move.basePower}${maxPower}${zStr}\nType: ${move.type} | Acc: ${move.accuracy === true ? "--" : move.accuracy} | Category: ${move.category} | PP: ${move.pp} (Max ${Math.floor(move.pp * 1.6)})\n${move.desc || move.shortDesc}${move.priority > 0 ? "\nPriority: +" + move.priority : move.priority < 0 ? "\nPriority: " + move.priority : ""}`, + description: `Base Power: ${move.basePower}${maxPower}${zStr}\nType: ${move.type} | Acc: ${move.accuracy === true ? "--" : move.accuracy} | Category: ${move.category} | PP: ${move.pp} (Max ${Math.floor(move.pp * 1.6)})\n${move.desc || move.shortDesc}${move.priority > 0 ? `\nPriority: +${move.priority}` : move.priority < 0 ? `\nPriority: ${move.priority}` : ""}`, url: `https://www.smogon.com/dex/${utilities.toSmogonString(dex.gen)}/moves/${(move.name.split(" ").join("-")).toLowerCase()}/`, author: { name: `${move.name}`, diff --git a/discord/commands/dex/pokedex.js b/discord/commands/dex/pokedex.js index 4c5ea79..09a507b 100644 --- a/discord/commands/dex/pokedex.js +++ b/discord/commands/dex/pokedex.js @@ -58,7 +58,7 @@ module.exports = { abilitiesStr.push(pokemon.abilities["1"]); } if (pokemon.abilities["H"]) { - abilitiesStr.push(pokemon.abilities["H"] + " (Hidden" + (pokemon.unreleasedHidden ? " [Unreleased]" : "") + ")"); + abilitiesStr.push(`${pokemon.abilities["H"]} (Hidden${pokemon.unreleasedHidden ? " [Unreleased]" : ""})`); } abilitiesStr.length === 1 ? abilitiesStr = 'Ability: ' + abilitiesStr[0] : abilitiesStr = `Abilities: ${abilitiesStr.join(", ")}`; //eslint-disable-line } @@ -156,7 +156,7 @@ module.exports = { const embed = { title: pokemon.name, - description: `HP: ${pokemon.baseStats.hp} / Atk: ${pokemon.baseStats.atk} / Def: ${pokemon.baseStats.def} / SpA: ${pokemon.baseStats.spa} / SpD: ${pokemon.baseStats.spd} / Spe: ${pokemon.baseStats.spe} (Total: ${bst})\n${abilitiesStr}\n${"Weight: " + pokemon.weightkg + "kg (" + lowKickCalcs(pokemon.weightkg) + " BP); Height: " + pokemon.heightm + "m"}`, + description: `HP: ${pokemon.baseStats.hp} / Atk: ${pokemon.baseStats.atk} / Def: ${pokemon.baseStats.def} / SpA: ${pokemon.baseStats.spa} / SpD: ${pokemon.baseStats.spd} / Spe: ${pokemon.baseStats.spe} (Total: ${bst})\n${abilitiesStr}\n${`Weight: ${pokemon.weightkg}kg (${lowKickCalcs(pokemon.weightkg)} BP); Height: ${pokemon.heightm}m`}`, url: `https://www.smogon.com/dex/${utilities.toSmogonString(dex.gen)}/pokemon/${(pokemon.name.split(" ").join("-")).toLowerCase()}/`, author: { name: `#${pokemon.num} - ${pokemon.name} [${pokemon.types.join("/")}]`, diff --git a/discord/commands/dex/sprite.js b/discord/commands/dex/sprite.js index fd65919..77ba4b7 100644 --- a/discord/commands/dex/sprite.js +++ b/discord/commands/dex/sprite.js @@ -7,7 +7,7 @@ const SPRITE_URL = "https://play.pokemonshowdown.com/sprites/"; module.exports = { desc: "Image link of a Pokémon, or link to sprite directory if no argument is given. Uses PokemonShowdown's sprite library.", usage: "", - aliases: ["gif", "model"], + aliases: ["gif", "model", "sprit"], options: ["shiny", "back", "female", "noani", "afd", "gen"], hasCustomFormatting: true, async process(message, args, dex) { @@ -56,7 +56,7 @@ module.exports = { dir += "-shiny"; } if (Tools.toId(args).includes("afd")) { - dir = "afd" + dir; + dir = `afd${dir}`; return `${SPRITE_URL}${dir}/${spriteId}.png`; } diff --git a/discord/commands/dice.js b/discord/commands/dice.js index e1eb246..ac44aba 100644 --- a/discord/commands/dice.js +++ b/discord/commands/dice.js @@ -13,7 +13,7 @@ module.exports = { `${discordConfig.commandCharacter}dice d<+/-> - Simulates rolling a number of dice and adding an offset to the sum, e.g., ${discordConfig.commandCharacter}dice 2d6+10: two standard dice are rolled; the result lies between 12 and 22.`, `${discordConfig.commandCharacter}dice d- - Simulates rolling a number of dice with removal of extreme values, e.g., /dice 3d8-L: rolls three 8-sided dice; the result ignores the lowest value.`], usage: "", - aliases: ["roll"], + aliases: ["roll", "d", "r"], async process(message, args) { let target = args.join(""); @@ -63,7 +63,7 @@ module.exports = { let maxRoll = 0; let minRoll = Number.MAX_SAFE_INTEGER; - const trackRolls = diceQuantity * (("" + diceFaces).length + 1) <= 60; + const trackRolls = diceQuantity * ((`${diceFaces}`).length + 1) <= 60; const rolls = []; let rollSum = 0; @@ -87,11 +87,11 @@ module.exports = { // Reply with relevant information let offsetFragment = ""; - if (offset) offsetFragment += (offset > 0 ? " + " + offset : offset); + if (offset) offsetFragment += (offset > 0 ? ` + ${offset}` : offset); - if (diceQuantity === 1) return message.channel.send(`Rolling (1 to ${diceFaces})${offsetFragment}: ${rollSum}`); + if (diceQuantity === 1) return message.channel.send(`:game_die: Rolling (1 to ${diceFaces})${offsetFragment}: ${rollSum}`); const outlierFragment = removeOutlier ? ` except ${removeOutlier > 0 ? "highest" : "lowest"}` : ``; - const rollsFragment = trackRolls ? ": " + rolls.join(", ") : ""; + const rollsFragment = trackRolls ? `: ${rolls.join(", ")}` : ""; return message.channel.send( `\`\`\`${diceQuantity} rolls (1 to ${diceFaces})${rollsFragment}\nSum${offsetFragment}${outlierFragment}: ${rollSum}\`\`\`` ); diff --git a/discord/commands/fc/addotherfc.js b/discord/commands/fc/addotherfc.js index be6b9de..2071018 100644 --- a/discord/commands/fc/addotherfc.js +++ b/discord/commands/fc/addotherfc.js @@ -5,7 +5,7 @@ const utilities = require("../../utilities.js"); module.exports = { desc: `Adds an FC to the provided category for the provided user, for use with ${discordConfig.commandCharacter}fc.`, usage: ", , ", - elevatedOnly: true, + isElevated: true, async process(message, args) { utilities.checkForDb("fc", `{"fc":{}}`); diff --git a/discord/commands/fc/removeotherfc.js b/discord/commands/fc/removeotherfc.js index 9ef923b..2950aff 100644 --- a/discord/commands/fc/removeotherfc.js +++ b/discord/commands/fc/removeotherfc.js @@ -6,7 +6,7 @@ module.exports = { desc: "Removes an FC from the database.", usage: ", , ", aliases: ["deleteotherfc"], - elevatedOnly: true, + isElevated: true, async process(message, args) { utilities.checkForDb("fc", `{"fc":{}}`); diff --git a/discord/commands/link.js b/discord/commands/link.js index efb83fc..82856c1 100644 --- a/discord/commands/link.js +++ b/discord/commands/link.js @@ -4,10 +4,10 @@ const utilities = require("../utilities.js"); module.exports = { usage: "<@user 1 | user id of user 1>, <@user 2 | user id of user 2> ... <@user x | user id of user x>", - desc: "Generates a random 4-digit link code and sends it to the author and provided users.", - longDesc: "Generates a random 4-digit link code and sends it to the author and provided users.\nPlease ensure you tag users or provide a valid user id (Right-Click/Long Press their username and press 'Copy ID' if you have developer tools enabled) as usernames alone are not understood.\nPlease also ensure that all involved parties are able to recieve Private Messages from the bot.", + desc: "Generates a random 8-digit link code and sends it to the author and provided users.", + longDesc: "Generates a random 8-digit link code and sends it to the author and provided users.\nPlease ensure you tag users or provide a valid user id (Right-Click/Long Press their username and press 'Copy ID' if you have developer tools enabled) as usernames alone are not understood.\nPlease also ensure that all involved parties are able to recieve Private Messages from the bot.", async process(message, args) { - const code = utilities.generateRandomLinkCode(4); + const code = utilities.generateRandomLinkCode(8); if (!(args[0])) { return message.channel.send(`Your random link code is: **${code}**`); diff --git a/discord/commands/linkcode.js b/discord/commands/linkcode.js index 7e2709d..489ff68 100644 --- a/discord/commands/linkcode.js +++ b/discord/commands/linkcode.js @@ -4,7 +4,7 @@ const utilities = require("../utilities.js"); module.exports = { desc: "Generates a random Link Code.", - longDesc: "Generates a unique link code of the specified length, otherwise 4.", + longDesc: "Generates a unique link code of the specified length, otherwise 8.", usage: "", aliases: ["code", "generaterandomlinkcode", "chooserandomlinkcode", "getrandomlinkcode"], async process(message, args) { diff --git a/discord/commands/management/ban.js b/discord/commands/management/ban.js new file mode 100644 index 0000000..5890a18 --- /dev/null +++ b/discord/commands/management/ban.js @@ -0,0 +1,60 @@ +"use strict"; + +const TIMEOUT = 30 * 1000; + +module.exports = { + desc: "Bans a user from the server.", + usage: "<@user|userid>, ", + isManager: true, + noPm: true, + async process(message, args) { + if (!args[0]) return message.channel.send(`Syntax: \`${discordConfig.commandCharacter}ban ${this.usage}\``); + + if (!message.guild.members.cache.get(client.user.id).hasPermission("BAN_MEMBERS")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions! Bot does not have the required \`BAN_MEMBERS\` permission.`); + (args[0].includes("<") ? client.users.fetch(args[0].match(/^<@!?(\d+)>$/)[1]) : client.users.fetch(args[0])).then(user => { + if (!user) return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}!`); + if (user.id === message.author.id) return message.channel.send(`${discordFailureEmoji} You cannot ban yourself!`); + + const member = message.guild.members.cache.get(user.id); + let banMessage = `${message.author} Are you sure you want to ban ${user.username}#${user.discriminator}? (Y/n):`; + if (member) { + if (!message.guild.members.cache.get(user.id).bannable) return message.channel.send(`${discordFailureEmoji} Unable to ban ${user.username}#${user.discriminator}: Insufficient permissions! Do they have a role higher than mine?`); + } else { + banMessage = `${message.author} It doesn't look like ${user.username}#${user.discriminator} is in this server. Would you like to ban them anyway? (Y/n):`; + } + + args.shift(); + const reason = args.join(", "); + + const filter = response => { + return response.author.id === message.author.id && response.channel.id === message.channel.id && (Tools.toId(response.content) === "y" || Tools.toId(response.content) === "n"); + }; + + try { + message.channel.send(banMessage).then(() => { + message.channel.awaitMessages(filter, {max: 1, time: TIMEOUT, errors: ["time"]}).then(collected => { + if (Tools.toId(collected.first().content) === "y") { + if (reason.length > 0) { + try { + user.send(`You have been banned from ${message.guild.name} ${reason.length > 0 ? `(${reason})` : `(No reason was provided)`}`); + } catch (e) { + message.channel.send("Unable to DM user."); + } + } + message.guild.members.ban(user, {reason: reason}); + return message.channel.send(`${discordSuccessEmoji} ${user} was banned${reason.length > 0 ? ` (Reason: ${reason})` : ""}!`); + } else { + message.channel.send(`${user.username}#${user.discriminator} will not be banned!`); + } + }).catch(collected => { + return message.channel.send(`The command timed out! ${user.username}#${user.discriminator} will not be banned!`); + }); + }); + } catch (e) { + console.log(e.stack); + } + }).catch(e => { + return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}! Please ensure you either mention the user, or provide a valid User ID.`); + }); + }, +}; diff --git a/discord/commands/management/channelmute.js b/discord/commands/management/channelmute.js new file mode 100644 index 0000000..60c0f76 --- /dev/null +++ b/discord/commands/management/channelmute.js @@ -0,0 +1,32 @@ +"use strict"; + +module.exports = { + desc: "Prevents a user from chatting in this channel.", + longDesc: "Adds a user-specific permissions override to prevent the mentioned user from talking and reacting in the channel that this command is used in.", + usage: "<@user|userid>", + aliases: ["chanmute", "mute"], + isManager: true, + noPm: true, + async process(message, args) { + if (!args[0]) return message.channel.send(`Syntax: \`${discordConfig.commandCharacter}channelmute ${this.usage}\``); + + if (!message.guild.members.cache.get(client.user.id).hasPermission("MANAGE_ROLES")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions! Bot does not have the required \`MANAGE_ROLES\` permission.`); + (args[0].includes("<") ? client.users.fetch(args[0].match(/^<@!?(\d+)>$/)[1]) : client.users.fetch(args[0])).then(user => { + if (!user) return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}!`); + try { + message.channel.updateOverwrite(user, { + "SEND_MESSAGES": false, + "ADD_REACTIONS": false, + "SPEAK": false, + "STREAM": false, + }); + return message.channel.send(`${discordSuccessEmoji} ${user} has been muted in ${message.channel}!`); + } catch (e) { + return message.channel.send(`${discordFailureEmoji} Unable to mute ${user}! Do they have a role higher than mine?`); + } + }).catch(e => { + console.log(e); + return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}! Please ensure you either mention the user, or provide a valid User ID.`); + }); + }, +}; diff --git a/discord/commands/management/channelunmute.js b/discord/commands/management/channelunmute.js new file mode 100644 index 0000000..3cb63ac --- /dev/null +++ b/discord/commands/management/channelunmute.js @@ -0,0 +1,31 @@ +"use strict"; + +module.exports = { + desc: "Undoes the effects of channelmute", + usage: "<@user|userid>", + aliases: ["chanunmute", "unmute"], + isManager: true, + noPm: true, + async process(message, args) { + if (!args[0]) return message.channel.send(`Syntax: \`${discordConfig.commandCharacter}channelunmute ${this.usage}\``); + + if (!message.guild.members.cache.get(client.user.id).hasPermission("MANAGE_ROLES")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions! Bot does not have the required \`MANAGE_ROLES\` permission.`); + (args[0].includes("<") ? client.users.fetch(args[0].match(/^<@!?(\d+)>$/)[1]) : client.users.fetch(args[0])).then(user => { + if (!user) return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}!`); + try { + message.channel.updateOverwrite(user, { + "SEND_MESSAGES": null, + "ADD_REACTIONS": null, + "SPEAK": null, + "STREAM": null, + }); + return message.channel.send(`${discordSuccessEmoji} ${user} has been unmuted in ${message.channel}!`); + } catch (e) { + return message.channel.send(`${discordFailureEmoji} Unable to unmute ${user}! Do they have a role higher than mine?`); + } + }).catch(e => { + console.log(e); + return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}! Please ensure you either mention the user, or provide a valid User ID.`); + }); + }, +}; diff --git a/discord/commands/management/editcommand.js b/discord/commands/management/editcommand.js index b7b8e3e..8dc984f 100644 --- a/discord/commands/management/editcommand.js +++ b/discord/commands/management/editcommand.js @@ -119,14 +119,14 @@ module.exports = { case "add": db.isElevated = true; Storage.exportDatabase(message.guild.id); - message.channel.send(`${successEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isElevated set to: **true**`); + message.channel.send(`${discordSuccessEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isElevated set to: **true**`); break; case "false": case "remove": case "delete": db.isElevated = false; Storage.exportDatabase(message.guild.id); - message.channel.send(`${successEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isElevated set to: **false**`); + message.channel.send(`${discordSuccessEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isElevated set to: **false**`); break; default: message.channel.send(`\`${discordConfig.commandCharacter}${command.name}\` - isElevated: ${db.isElevated}`); @@ -142,14 +142,14 @@ module.exports = { case "add": db.isManager = true; Storage.exportDatabase(message.guild.id); - message.channel.send(`${successEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isManager set to: **true**`); + message.channel.send(`${discordSuccessEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isManager set to: **true**`); break; case "false": case "remove": case "delete": db.isManager = false; Storage.exportDatabase(message.guild.id); - message.channel.send(`${successEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isManager set to: **false**`); + message.channel.send(`${discordSuccessEmoji} \`${discordConfig.commandCharacter}${command.name}\` - isManager set to: **false**`); break; default: message.channel.send(`\`${discordConfig.commandCharacter}${command.name}\` - isManager: ${db.isManager}`); @@ -161,7 +161,7 @@ module.exports = { } return true; } else { - return message.channel.send(`${failureEmoji} Command: "${Tools.toId(args[0])}" not found!`); + return message.channel.send(`${discordFailureEmoji} Command: "${Tools.toId(args[0])}" not found!`); } }, }; @@ -173,19 +173,19 @@ function list(message, cmd, area) { for (const entry of db[area]) { dbArr.push(`${utilities.parseRoleId(message, entry).name} (${entry})`); } - message.channel.send(`${area} (${message.guild.name} - \`${discordConfig.commandCharacter}${cmd}\`)\n\`\`\`${dbArr.length === 0 ? "None!" : "\t• " + dbArr.join("\n\t• ")}\`\`\``); + message.channel.send(`${area} (${message.guild.name} - \`${discordConfig.commandCharacter}${cmd}\`)\n\`\`\`${dbArr.length === 0 ? "None!" : `\t• ${dbArr.join("\n\t• ")}`}\`\`\``); } if (area === "bannedUsers") { for (const entry of db[area]) { dbArr.push(`${utilities.parseUserId(entry).username}#${utilities.parseUserId(entry).discriminator} (${entry})`); } - message.channel.send(`${area} (${message.guild.name} - \`${discordConfig.commandCharacter}${cmd}\`)\n\`\`\`${dbArr.length === 0 ? "None!" : "\t• " + dbArr.join("\n\t• ")}\`\`\``); + message.channel.send(`${area} (${message.guild.name} - \`${discordConfig.commandCharacter}${cmd}\`)\n\`\`\`${dbArr.length === 0 ? "None!" : `\t• ${dbArr.join("\n\t• ")}`}\`\`\``); } if (area === "bannedChannels") { for (const entry of db[area]) { dbArr.push(`${utilities.parseChannelId(message, entry).name} (${entry})`); } - message.channel.send(`${area} (${message.guild.name} - \`${discordConfig.commandCharacter}${cmd}\`)\n\`\`\`${dbArr.length === 0 ? "None!" : "\t•" + dbArr.join("\n\t• ")}\`\`\``); + message.channel.send(`${area} (${message.guild.name} - \`${discordConfig.commandCharacter}${cmd}\`)\n\`\`\`${dbArr.length === 0 ? "None!" : `\t•${dbArr.join("\n\t• ")}`}\`\`\``); } return true; } @@ -199,11 +199,11 @@ function add(message, cmd, area, args) { if (area === "bannedChannels") id = utilities.parseChannelId(message, arg); if (id && !(db[area].includes(id.id))) { db[area].push(id.id); - message.channel.send(`${successEmoji} Added "${arg}" to **${area}** for command \`${discordConfig.commandCharacter}${cmd}\`!`); + message.channel.send(`${discordSuccessEmoji} Added "${arg}" to **${area}** for command \`${discordConfig.commandCharacter}${cmd}\`!`); } else { const text = db[area].includes(id.id) ? - `${failureEmoji} "${arg}" is already in the group: ${area} for \`${discordConfig.commandCharacter}${cmd}\`!` : - `${failureEmoji} Unable to find a match for "${arg}".`; + `${discordFailureEmoji} "${arg}" is already in the group: ${area} for \`${discordConfig.commandCharacter}${cmd}\`!` : + `${discordFailureEmoji} Unable to find a match for "${arg}".`; message.channel.send(text); } } @@ -219,11 +219,11 @@ function remove(message, cmd, area, args) { if (area === "bannedChannels") id = utilities.parseChannelId(message, arg); if (id && (db[area].includes(id.id))) { db[area].splice(db[area].indexOf(id.id), 1); - message.channel.send(`${successEmoji} Removed "${arg}" from **${area}** for command \`${discordConfig.commandCharacter}${cmd}\`!`); + message.channel.send(`${discordSuccessEmoji} Removed "${arg}" from **${area}** for command \`${discordConfig.commandCharacter}${cmd}\`!`); } else { const text = db[area].includes(id.id) ? - `${failureEmoji} Unable to find a match for "${arg}".` : - `${failureEmoji} "${arg}" is not in the group: ${area} for \`${discordConfig.commandCharacter}${cmd}\`!`; + `${discordFailureEmoji} Unable to find a match for "${arg}".` : + `${discordFailureEmoji} "${arg}" is not in the group: ${area} for \`${discordConfig.commandCharacter}${cmd}\`!`; message.channel.send(text); } } diff --git a/discord/commands/management/elevated.js b/discord/commands/management/elevated.js index 01ac7cc..04305eb 100644 --- a/discord/commands/management/elevated.js +++ b/discord/commands/management/elevated.js @@ -18,13 +18,13 @@ module.exports = { if (user) { if (!db.config.botRanks.elevated.includes(user.id)) { db.config.botRanks.elevated.push(user.id); - message.channel.send(`${successEmoji} Game <@${user.id}> elevated permissions!`); + message.channel.send(`${discordSuccessEmoji} Gave <@${user.id}> elevated permissions!`); Storage.exportDatabase(message.guild.id); } else { - message.channel.send(`${failureEmoji} ${user.username}#${user.discriminator} already has elevated permissions!`); + message.channel.send(`${discordFailureEmoji} ${user.username}#${user.discriminator} already has elevated permissions!`); } } else { - message.channel.send(`${failureEmoji} User "${args[i]}" not found...`); + message.channel.send(`${discordFailureEmoji} User "${args[i]}" not found...`); } } break; @@ -34,13 +34,13 @@ module.exports = { if (user) { if (db.config.botRanks.elevated.includes(user.id)) { db.config.botRanks.elevated.splice(db.config.botRanks.elevated.indexOf(user.id), 1); - message.channel.send(`${successEmoji} Removed elevated permissions from <@${user.id}>.`); + message.channel.send(`${discordSuccessEmoji} Removed elevated permissions from <@${user.id}>.`); Storage.exportDatabase(message.guild.id); } else { - message.channel.send(`${failureEmoji} ${user.username}#${user.discriminator} does not have elevated permissions!`); + message.channel.send(`${discordFailureEmoji} ${user.username}#${user.discriminator} does not have elevated permissions!`); } } else { - message.channel.send(`${failureEmoji} User "${args[i]}" not found...`); + message.channel.send(`${discordFailureEmoji} User "${args[i]}" not found...`); } } break; @@ -66,7 +66,7 @@ module.exports = { buf += `\t${user.username}#${user.discriminator} (server)\n`; } } - buf += "```"; + buf += "\nBy default, all user with the Server Administrator permission are also Bot Managers and have Elevated Permissions, even if not listed above.\n```"; message.channel.send(buf); break; } diff --git a/discord/commands/management/filter.js b/discord/commands/management/filter.js new file mode 100644 index 0000000..d1675fe --- /dev/null +++ b/discord/commands/management/filter.js @@ -0,0 +1,35 @@ +"use strict"; + +module.exports = { + desc: "Allows configuration of the word filter.", + usage: "<+/-word1/regexp1>, <+/-word2/regexp2 (optional)>, <+/-word/regexp... (optional)>", + aliases: ["banword"], + isManager: true, + noPm: true, + async process(message, args) { + const db = Storage.getDatabase(message.guild.id); + if (!db.filter) db.filter = []; + const words = args.join(" "); + const errorWords = []; + for (let word of words.split(" ")) { + word = Tools.toFilterWord(word); + const w = word.substring(1); + if (word.startsWith("+") && !db.filter.includes(w)) { + try { + const r = new RegExp(w); // eslint-disable-line + db.filter.push(w); + } catch (e) { + errorWords.push(w); + } + } else if (word.startsWith("-") && db.filter.includes(word.substring(1))) { + db.filter.splice(db.filter.indexOf(word.substring(1)), 1); + } else { + if (word.length > 0) message.channel.send(`${word} is${db.filter.includes(word) ? "" : " not"} a banned word in this server.`); + } + } + db.filter.sort(); + Storage.exportDatabase(message.guild.id); + if (errorWords.length > 0) message.channel.send(`Failed to add: \`\`\`${errorWords.join("\n")}\`\`\` as ${errorWords.length === 1 ? "it" : "they"} could not be parsed as a valid regular expression!`); + return message.channel.send(`\`\`\`md\nFiltered Words in ${message.guild.name}:\n\t${db.filter.length > 0 ? db.filter.join("\n\t") : "None!"}\`\`\``); + }, +}; diff --git a/discord/commands/management/info.js b/discord/commands/management/info.js index 226d2d3..2fb41e7 100644 --- a/discord/commands/management/info.js +++ b/discord/commands/management/info.js @@ -1,5 +1,7 @@ "use strict"; +const utilities = require("../../utilities.js"); + module.exports = { desc: "Provides info on the provided command.", usage: ", <(optional) nopm>", @@ -30,14 +32,32 @@ module.exports = { for (let i = 0; i < userArr.length; i++) { userArr[i] = `${userArr[i].name} - ${userArr[i].times} time${userArr[i].times === 1 ? "" : "s"}`; } + + // List Role names as well as IDs + const requiredRoles = []; + for (let i = 0; i < db.requiredRoles.length; i++) { + requiredRoles.push(`${utilities.parseRoleId(message, db.requiredRoles[i]).name} (${db.requiredRoles[i]})`); + } + // List User names as well as IDs + const bannedUsers = []; + for (let i = 0; i < db.bannedUsers.length; i++) { + const tempUser = utilities.parseUserId(db.bannedUsers[i]); + bannedUsers.push(`${tempUser.username}#${tempUser.discriminator} (${db.bannedUsers[i]})`); + } + // List Channel names as well as IDs + const bannedChannels = []; + for (let i = 0; i < db.bannedChannels.length; i++) { + bannedChannels.push(`#${utilities.parseChannelId(message, db.bannedChannels[i]).name} (${db.bannedChannels[i]})`); + } + const embed = { title: `Info for: ${discordConfig.commandCharacter}${command.name} (#${message.guild.name})`, description: `**About**: ${command.longDesc || command.desc || "No info available."}`, color: message.guild.members.cache.get(client.user.id).displayColor, fields: [ - {name: "requiredRoles", value: db.requiredRoles.join(", ") || "None", inline: true}, - {name: "bannedUsers", value: db.bannedUsers.join(", ") || "None", inline: true}, - {name: "bannedChannels", value: db.bannedChannels.join(", ") || "None", inline: true}, + {name: "requiredRoles", value: requiredRoles.join(",\n") || "None", inline: true}, + {name: "bannedUsers", value: bannedUsers.join(",\n") || "None", inline: true}, + {name: "bannedChannels", value: bannedChannels.join(",\n") || "None", inline: true}, {name: "isElevated", value: db.isElevated, inline: true}, {name: "isManager", value: db.isManager, inline: true}, {name: "Uses", value: `Total: ${db.uses.total}\nBreakdown:\n${userArr.join("\n")}`}, diff --git a/discord/commands/management/kick.js b/discord/commands/management/kick.js new file mode 100644 index 0000000..99e0847 --- /dev/null +++ b/discord/commands/management/kick.js @@ -0,0 +1,46 @@ +"use strict"; + +const TIMEOUT = 30 * 1000; + +module.exports = { + desc: "Kicks a user from the server.", + usage: "<@user|userid>, ", + isElevated: true, + noPm: true, + async process(message, args) { + if (!args[0]) return message.channel.send(`Syntax: \`${discordConfig.commandCharacter}kick ${this.usage}\``); + + if (!message.guild.members.cache.get(client.user.id).hasPermission("KICK_MEMBERS")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions! Bot does not have the required \`KICK_MEMBERS\` permission.`); + (args[0].includes("<") ? client.users.fetch(args[0].match(/^<@!?(\d+)>$/)[1]) : client.users.fetch(args[0])).then(user => { + if (!user) return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}!`); + if (user.id === message.author.id) return message.channel.send(`${discordFailureEmoji} You cannot kick yourself!`); + if (!message.guild.members.cache.get(user.id).bannable) return message.channel.send(`${discordFailureEmoji} Unable to kick ${user.username}#${user.discriminator}: Insufficient permissions! Do they have a role higher than mine?`); + + args.shift(); + const reason = args.join(", "); + + const filter = response => { + return response.author.id === message.author.id && response.channel.id === message.channel.id && (Tools.toId(response.content) === "y" || Tools.toId(response.content) === "n"); + }; + + try { + message.channel.send(`${message.author} Are you sure you want to kick ${user.username}#${user.discriminator}? (Y/n):`).then(() => { + message.channel.awaitMessages(filter, {max: 1, time: TIMEOUT, errors: ["time"]}).then(collected => { + if (Tools.toId(collected.first().content) === "y") { + message.guild.members.kick(user, {reason: reason}); + return message.channel.send(`${discordSuccessEmoji} ${user} was kicked${reason.length > 0 ? ` (${reason})` : ""}!`); + } else { + message.channel.send(`${user.username}#${user.discriminator} will not be kicked!`); + } + }).catch(collected => { + return message.channel.send(`The command timed out! ${user.username}#${user.discriminator} will not be kicked!`); + }); + }); + } catch (e) { + console.log(e.stack); + } + }).catch(e => { + return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}! Please ensure you either mention the user, or provide a valid User ID.`); + }); + }, +}; diff --git a/discord/commands/management/logger.js b/discord/commands/management/logger.js index aed984f..016358c 100644 --- a/discord/commands/management/logger.js +++ b/discord/commands/management/logger.js @@ -12,17 +12,17 @@ module.exports = { const db = Storage.getDatabase(message.guild.id); switch (Tools.toId(args[0])) { case "on": - if (!args[1] || !utilities.parseChannelId(message, args[1])) return message.channel.send(`${failureEmoji} Expected a channel!`); + if (!args[1] || !utilities.parseChannelId(message, args[1])) return message.channel.send(`${discordFailureEmoji} Expected a channel!`); delete db.config.logger; db.config.logger = {"logDeletes": true, "deletesChannel": utilities.parseChannelId(message, args[1]).id, "logEdits": true, "editsChannel": utilities.parseChannelId(message, args[1]).id}; - message.channel.send(`${successEmoji} Enabled event logging in #${utilities.parseChannelId(message, args[1]).name}`); + message.channel.send(`${discordSuccessEmoji} Enabled event logging in #${utilities.parseChannelId(message, args[1]).name}`); break; case "off": delete db.config.logger; - message.channel.send(`${successEmoji} Disabled event logging!`); + message.channel.send(`${discordSuccessEmoji} Disabled event logging!`); break; default: - message.channel.send(`Currently logging events: ${db.config.logger ? db.config.logger.logDeletes + " in channel: " + utilities.parseChannelId(message, db.config.logger.deletesChannel).name : "false"}`); + message.channel.send(`Currently logging events: ${db.config.logger ? `${db.config.logger.logDeletes} in channel: ${utilities.parseChannelId(message, db.config.logger.deletesChannel).name}` : "false"}`); } Storage.exportDatabase(message.guild.id); return true; diff --git a/discord/commands/management/managers.js b/discord/commands/management/managers.js index 1b87c39..eb557ae 100644 --- a/discord/commands/management/managers.js +++ b/discord/commands/management/managers.js @@ -18,13 +18,13 @@ module.exports = { if (user) { if (!db.config.botRanks.manager.includes(user.id)) { db.config.botRanks.manager.push(user.id); - message.channel.send(`${successEmoji} Added <@${user.id}> as a Bot Manager!`); + message.channel.send(`${discordSuccessEmoji} Added <@${user.id}> as a Bot Manager!`); Storage.exportDatabase(message.guild.id); } else { - message.channel.send(`${failureEmoji} ${user.username}#${user.discriminator} is already a Bot Manager!`); + message.channel.send(`${discordFailureEmoji} ${user.username}#${user.discriminator} is already a Bot Manager!`); } } else { - message.channel.send(`${failureEmoji} User "${args[i]}" not found...`); + message.channel.send(`${discordFailureEmoji} User "${args[i]}" not found...`); } } break; @@ -35,13 +35,13 @@ module.exports = { if (user) { if (db.config.botRanks.manager.includes(user.id)) { db.config.botRanks.manager.splice(db.config.botRanks.manager.indexOf(user.id), 1); - message.channel.send(`${successEmoji} Removed Bot Manager permission from <@${user.id}>.`); + message.channel.send(`${discordSuccessEmoji} Removed Bot Manager permission from <@${user.id}>.`); Storage.exportDatabase(message.guild.id); } else { - message.channel.send(`${failureEmoji} ${user.username}#${user.discriminator} is not a Bot Manager!`); + message.channel.send(`${discordFailureEmoji} ${user.username}#${user.discriminator} is not a Bot Manager!`); } } else { - message.channel.send(`${failureEmoji} User "${args[i]}" not found...`); + message.channel.send(`${discordFailureEmoji} User "${args[i]}" not found...`); } } break; @@ -56,7 +56,7 @@ module.exports = { const user = utilities.parseUserId(db.config.botRanks.manager[i]); buf += `\t${user.username}#${user.discriminator} (server)\n`; } - buf += "```"; + buf += "\nBy default, all user with the Server Administrator permission are also Bot Managers, even if not listed above.\n```"; message.channel.send(buf); break; } diff --git a/discord/commands/management/nickname.js b/discord/commands/management/nickname.js index 32acff3..77d0417 100644 --- a/discord/commands/management/nickname.js +++ b/discord/commands/management/nickname.js @@ -18,16 +18,16 @@ module.exports = { if (user) { await message.guild.members.cache.get(user).setNickname(args[0].trim()).catch(function () { failed = true; }); if (!failed) { - return message.channel.send(`${discordConfig.successEmoji} Set <@${user}>'s nickname to "${args[0].trim()}"!`); + return message.channel.send(`${discordSuccessEmoji} Set <@${user}>'s nickname to "${args[0].trim()}"!`); } else { - return message.channel.send(`${discordConfig.failureEmoji} Unable to set nickname!`); + return message.channel.send(`${discordFailureEmoji} Unable to set nickname!`); } } else { await message.guild.members.cache.get(client.user.id).setNickname(args[0].trim()).catch(function () { failed = true; }); if (!failed) { - return message.channel.send(`${discordConfig.successEmoji} Set <@${client.user.id}>'s nickname to "${args[0].trim()}"!`); + return message.channel.send(`${discordSuccessEmoji} Set <@${client.user.id}>'s nickname to "${args[0].trim()}"!`); } else { - return message.channel.send(`${discordConfig.failureEmoji} Unable to set nickname!`); + return message.channel.send(`${discordFailureEmoji} Unable to set nickname!`); } } }, diff --git a/discord/commands/management/ping.js b/discord/commands/management/ping.js new file mode 100644 index 0000000..e5b559a --- /dev/null +++ b/discord/commands/management/ping.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + desc: "Prints information about the bot's ping.", + async process(message, args) { + return message.channel.send(`🏓 Pong!\nBot: ${Date.now() - message.createdTimestamp}ms | API: ${client.ws.ping}ms`); + }, +}; diff --git a/discord/commands/management/purge.js b/discord/commands/management/purge.js new file mode 100644 index 0000000..f814e85 --- /dev/null +++ b/discord/commands/management/purge.js @@ -0,0 +1,60 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +const TIMEOUT = 30 * 1000; + +module.exports = { + desc: "Removes many messages from a channel.", + longDesc: "Removes up to the last 1 or more messages in a channel. If an author is provided, only delete messages by that user.\nOnly the most recent 100 messages can be accessed at once, so the number of actual deleted messages may be lower than the provided number.\nMessages older than two weeks cannot be deleted in this manner.", + usage: ", <@author|author id (optional, default none)>", + aliases: ["delete", "wipe", "bulkdelete", "deletemany"], + isManager: true, + noPm: true, + async process(message, args) { + if (!message.guild.members.cache.get(client.user.id).hasPermission("MANAGE_MESSAGES")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions! Bot does not have the required \`MANAGE_MESSAGES\` permission.`); + const users = []; + let num = 1; + for (let arg of args) { + arg = arg.trim(); + const tempUser = utilities.parseUserId(arg); + if (tempUser) { + users.push(tempUser); + } else { + const tempNum = parseInt(arg); + if (!isNaN(tempNum)) num = tempNum; + } + } + + num = Math.min(Math.max(num, 1), 100); + // Get confirmation before proceeding + const filter = response => { + return response.author.id === message.author.id && response.channel.id === message.channel.id && (Tools.toId(response.content) === "y" || Tools.toId(response.content) === "n"); + }; + try { + message.channel.send(`Are you sure you want to delete the last ${num === 1 ? "" : `${num} `}message${num === 1 ? "" : "s"} ${users.length > 0 ? `by ${users[0]}` : ""}? (Y/n):`).then(() => { + message.channel.awaitMessages(filter, {max: 1, time: TIMEOUT, errors: ["time"]}).then(async (collected) => { + if (Tools.toId(collected.first().content) === "y") { + let messages = await message.channel.messages.fetch({limit: 100}).catch(e => { console.log(e); return message.channel.send(`${discordFailureEmoji} Error fetching messages!`); }); + if (users[0]) { + messages = messages.filter(m => m.author.id === users[0].id); + } + messages = messages.array().slice(0, num); + try { + const deleted = await message.channel.bulkDelete(messages, true); + message.channel.send(`${discordSuccessEmoji} ${deleted.size} message${deleted.size === 1 ? " was" : "s were"} deleted!`); + } catch (e) { + console.log(e.stack); + } + } else { + message.channel.send("No messages will be deleted!"); + } + }).catch(collected => { + return message.channel.send("The command timed out! No messages will be deleted."); + }); + }); + } catch (e) { + console.log(`${Tools.discordText()}Something went wrong...`); + } + }, +}; diff --git a/discord/commands/management/removecolor.js b/discord/commands/management/removecolor.js new file mode 100644 index 0000000..7d0ae78 --- /dev/null +++ b/discord/commands/management/removecolor.js @@ -0,0 +1,29 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +module.exports = { + desc: "Removes the user's custom role color.", + aliases: ["removecolour", "deletecolor", "deletecolour", "deletenamecolor", "deletenamecolour", "removerole", "deleterole"], + async process(message, args) { + if (!message.guild.members.cache.get(client.user.id).hasPermission("MANAGE_ROLES")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions!`); + const db = Storage.getDatabase(message.guild.id); + if (!db.customRoles) db.customRoles = {}; + if (!db.customRoles[message.author.id]) db.customRoles[message.author.id] = {}; + db.customRoles[message.author.id].name = `${message.author.username}#${message.author.discriminator}`; + if (db.customRoles[message.author.id].roleId) { + try { + const oldRole = utilities.parseRoleId(message, db.customRoles[message.author.id].roleId); + if (oldRole) await oldRole.delete("Old custom color role"); + message.channel.send(`${discordSuccessEmoji} Successfully removed your custom role!`); + } catch (e) { + message.channel.send(`${discordFailureEmoji} Something went wrong deleting your old role!`); + } + db.customRoles[message.author.id].roleId = ""; + } else { + return message.channel.send(`${discordFailureEmoji} You don't have a custom color! Please run \`${discordConfig.commandCharacter}rolecolor\` first.`); + } + Storage.exportDatabase(message.guild.id); + return true; + }, +}; diff --git a/discord/commands/management/rolecolor.js b/discord/commands/management/rolecolor.js new file mode 100644 index 0000000..7020b46 --- /dev/null +++ b/discord/commands/management/rolecolor.js @@ -0,0 +1,61 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +module.exports = { + desc: "Gives the user a custom role color.", + usage: "", + aliases: ["rolecolour", "customcolor", "customcolour", "namecolor", "namecolour"], + async process(message, args) { + if (!message.guild.members.cache.get(client.user.id).hasPermission("MANAGE_ROLES")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions!`); + const db = Storage.getDatabase(message.guild.id); + if (!db.customRoles) db.customRoles = {}; + if (!db.customRoles[message.author.id]) db.customRoles[message.author.id] = {}; + db.customRoles[message.author.id].name = `${message.author.username}#${message.author.discriminator}`; + + const hexRegex = /^(0[xX]){1}[A-Fa-f0-9]{6}$|^#[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{6}$/; + let color = args[0]; + if (color.trim().length > 8) { + color = color.trim().substr(0, 8); + } + color = color.replace("0x", "#"); + if (!color.startsWith("#")) color = `#${color}`; + color = color.toUpperCase(); + if (color.includes("36393E")) return message.channel.send(`${discordFailureEmoji} Unable to set your custom color! Try a color that can be seen in all themes next time.`); + if (!(hexRegex.test(color.trim()))) return message.channel.send(`${discordFailureEmoji} Unable to coerce "${args[0]}" as a hex code!`); + + // Check for existing custom role + if (db.customRoles[message.author.id].roleId && utilities.parseRoleId(message, db.customRoles[message.author.id].roleId) && !utilities.parseRoleId(message, db.customRoles[message.author.id].roleId).deleted) { + const role = utilities.parseRoleId(message, db.customRoles[message.author.id].roleId); + await role.edit({ + name: `${message.author.username} - ${color}`, + color: color, + position: message.guild.members.cache.get(client.user.id).roles.highest.position - 1, + }, "Custom Role Color (Update)").catch(console.error); + message.channel.send(`${discordSuccessEmoji} Successfully updated <@${message.author.id}>'s color to ${color}!`); + } else { + // Make a new custom role + await message.guild.roles.create({ + data: { + name: `${message.author.username} - ${color}`, + color: color, + position: message.guild.members.cache.get(client.user.id).roles.highest.position - 1, + }, + reason: "Custom Role Color", + }).catch(console.error); + + const role = utilities.parseRoleId(message, `${message.author.username} - ${color}`); + db.customRoles[message.author.id].roleId = role.id; + + try { + await message.member.roles.add(role); + message.channel.send(`${discordSuccessEmoji} Successfully added ${color} to <@${message.author.id}>`); + } catch (e) { + role.delete(); + message.channel.send(`${discordFailureEmoji} Something went wrong creating your role!`); + } + } + Storage.exportDatabase(message.guild.id); + return true; + }, +}; diff --git a/discord/commands/management/servermute.js b/discord/commands/management/servermute.js new file mode 100644 index 0000000..5b7bbf3 --- /dev/null +++ b/discord/commands/management/servermute.js @@ -0,0 +1,39 @@ +"use strict"; + +const TIMEOUT = 30 * 1000; + +module.exports = { + desc: "Prevents a user from chatting in all channels.", + longDesc: "Adds a user-specific permissions override to every text and voice channel in the server that prevents them from talking, reacting, and streaming.", + usage: "<@user|userid>", + isManager: true, + noPm: true, + async process(message, args) { + if (!args[0]) return message.channel.send(`Syntax: \`${discordConfig.commandCharacter}servermute ${this.usage}\``); + + if (!message.guild.members.cache.get(client.user.id).hasPermission("MANAGE_ROLES")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions! Bot does not have the required \`MANAGE_ROLES\` permission.`); + (args[0].includes("<") ? client.users.fetch(args[0].match(/^<@!?(\d+)>$/)[1]) : client.users.fetch(args[0])).then(user => { + if (!user) return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}!`); + try { + message.channel.startTyping(); + for (const channel of message.guild.channels.cache.values()) { + if (!["text", "voice"].includes(channel.type)) continue; + channel.updateOverwrite(user, { + "SEND_MESSAGES": false, + "ADD_REACTIONS": false, + "SPEAK": false, + "STREAM": false, + }); + } + message.channel.stopTyping(); + return message.channel.send(`${discordSuccessEmoji} ${user} has been muted in all text and voice channels!`); + } catch (e) { + message.channel.stopTyping(); + return message.channel.send(`${discordFailureEmoji} Unable to mute ${user}! Do they have a role higher than mine?`); + } + }).catch(e => { + console.log(e); + return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}! Please ensure you either mention the user, or provide a valid User ID.`); + }); + }, +}; diff --git a/discord/commands/management/serverunmute.js b/discord/commands/management/serverunmute.js new file mode 100644 index 0000000..096a3dd --- /dev/null +++ b/discord/commands/management/serverunmute.js @@ -0,0 +1,41 @@ +"use strict"; + +const TIMEOUT = 30 * 1000; + +module.exports = { + desc: "Undoes the effects of servermute.", + usage: "<@user|userid>", + isManager: true, + noPm: true, + async process(message, args) { + if (!args[0]) return message.channel.send(`Syntax: \`${discordConfig.commandCharacter}serverunmute ${this.usage}\``); + + if (!message.guild.members.cache.get(client.user.id).hasPermission("MANAGE_ROLES")) return message.channel.send(`${discordFailureEmoji} Insufficient permissions! Bot does not have the required \`MANAGE_ROLES\` permission.`); + (args[0].includes("<") ? client.users.fetch(args[0].match(/^<@!?(\d+)>$/)[1]) : client.users.fetch(args[0])).then(async user => { + if (!user) return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}!`); + try { + message.channel.startTyping(); + for (const channel of message.guild.channels.cache.values()) { + if (!["text", "voice"].includes(channel.type)) continue; + await channel.updateOverwrite(user, { + "SEND_MESSAGES": null, + "ADD_REACTIONS": null, + "SPEAK": null, + "STREAM": null, + }); + const perms = await channel.permissionOverwrites.get(user.id); + if (!perms) continue; + if (perms.allow.bitfield === 0 && perms.deny.bitfield === 0) await perms.delete(); + } + message.channel.stopTyping(); + return message.channel.send(`${discordSuccessEmoji} ${user} has been unmuted in all text and voice channels!`); + } catch (e) { + message.channel.stopTyping(); + return message.channel.send(`${discordFailureEmoji} Unable to unmute ${user}! Do they have a role higher than mine?`); + } + }).catch(e => { + console.log(e); + return message.channel.send(`${discordFailureEmoji} Unable to find user: ${args[0]}! Please ensure you either mention the user, or provide a valid User ID.`); + }); + }, +}; diff --git a/discord/commands/management/settings.js b/discord/commands/management/settings.js index cc15656..4765cca 100644 --- a/discord/commands/management/settings.js +++ b/discord/commands/management/settings.js @@ -23,10 +23,10 @@ module.exports = { if (!(args[2])) return message.channel.send("Please mention/provide the id of the role you would like to add!"); for (let i = 2; i < args.length; i++) { const role = utilities.parseRoleId(message, args[i]); - if (!role) return message.channel.send(`${failureEmoji} Role "${args[i]}" not found...`); - if (db.config.requiredRoles.includes(role.id)) return message.channel.send(`${failureEmoji} @${role.name} is already a Required Role!`); + if (!role) return message.channel.send(`${discordFailureEmoji} Role "${args[i]}" not found...`); + if (db.config.requiredRoles.includes(role.id)) return message.channel.send(`${discordFailureEmoji} @${role.name} is already a Required Role!`); db.config.requiredRoles.push(role.id); - message.channel.send(`${successEmoji} Added @${role.name} as a required role!`); + message.channel.send(`${discordSuccessEmoji} Added @${role.name} as a required role!`); } Storage.exportDatabase(message.guild.id); break; @@ -35,10 +35,10 @@ module.exports = { if (!(args[2])) return message.channel.send("Please mention/provide the id of the role you would like to remove!"); for (let i = 2; i < args.length; i++) { const role = utilities.parseRoleId(message, args[i]); - if (!role) return message.channel.send(`${failureEmoji} Role "${args[i]}" not found...`); - if (!db.config.requiredRoles.includes(role.id)) return message.channel.send(`${failureEmoji} @${role.name} is not a Required Role!`); + if (!role) return message.channel.send(`${discordFailureEmoji} Role "${args[i]}" not found...`); + if (!db.config.requiredRoles.includes(role.id)) return message.channel.send(`${discordFailureEmoji} @${role.name} is not a Required Role!`); db.config.requiredRoles.splice(db.config.requiredRoles.indexOf(role.id), 1); - message.channel.send(`${successEmoji} Removed @${role.name} as a required role!`); + message.channel.send(`${discordSuccessEmoji} Removed @${role.name} as a required role!`); } Storage.exportDatabase(message.guild.id); break; @@ -61,10 +61,10 @@ module.exports = { if (!(args[2])) return message.channel.send("Please mention/provide the id of the channel you would like to ban!"); for (let i = 2; i < args.length; i++) { const chan = utilities.parseChannelId(message, args[i]); - if (!chan) return message.channel.send(`${failureEmoji} Channel "${args[i]}" not found...`); - if (db.config.bannedChannels.includes(chan.id)) return message.channel.send(`${failureEmoji} #${chan.name} is already a Banned Channel!`); + if (!chan) return message.channel.send(`${discordFailureEmoji} Channel "${args[i]}" not found...`); + if (db.config.bannedChannels.includes(chan.id)) return message.channel.send(`${discordFailureEmoji} #${chan.name} is already a Banned Channel!`); db.config.bannedChannels.push(chan.id); - message.channel.send(`${successEmoji} Added #${chan.name} as a banned channel!`); + message.channel.send(`${discordSuccessEmoji} Added #${chan.name} as a banned channel!`); } Storage.exportDatabase(message.guild.id); break; @@ -74,10 +74,10 @@ module.exports = { if (!(args[2])) return message.channel.send("Please mention/provide the id of the channel you would like to unban!"); for (let i = 2; i < args.length; i++) { const chan = utilities.parseChannelId(message, args[i]); - if (!chan) return message.channel.send(`${failureEmoji} Channel "${args[i]}" not found...`); - if (!db.config.bannedChannels.includes(chan.id)) return message.channel.send(`${failureEmoji} #${chan.name} is not a Banned Channel!`); + if (!chan) return message.channel.send(`${discordFailureEmoji} Channel "${args[i]}" not found...`); + if (!db.config.bannedChannels.includes(chan.id)) return message.channel.send(`${discordFailureEmoji} #${chan.name} is not a Banned Channel!`); db.config.bannedChannels.splice(db.config.bannedChannels.indexOf(chan.id), 1); - message.channel.send(`${successEmoji} Removed #${chan.name} as a banned channel!`); + message.channel.send(`${discordSuccessEmoji} Removed #${chan.name} as a banned channel!`); } Storage.exportDatabase(message.guild.id); break; @@ -93,15 +93,15 @@ module.exports = { case "add": case "true": db.config.nsfw.allowNSFW = true; - message.channel.send(`${discordConfig.successEmoji} NSFW commands enabled!`); + message.channel.send(`${discordSuccessEmoji} NSFW commands enabled!`); Storage.exportDatabase(message.guild.id); if (!(args[2])) return message.channel.send("Please mention/provide the id of the channel you would like to add!"); for (let i = 2; i < args.length; i++) { const chan = utilities.parseChannelId(message, args[i]); - if (!chan) return message.channel.send(`${failureEmoji} Channel "${args[i]}" not found...`); - if (db.config.nsfw.nsfwChannels.includes(chan.id)) return message.channel.send(`${failureEmoji} #${chan.name} already has NSFW commands available!`); + if (!chan) return message.channel.send(`${discordFailureEmoji} Channel "${args[i]}" not found...`); + if (db.config.nsfw.nsfwChannels.includes(chan.id)) return message.channel.send(`${discordFailureEmoji} #${chan.name} already has NSFW commands available!`); db.config.nsfw.nsfwChannels.push(chan.id); - message.channel.send(`${successEmoji} Enabled NSFW commands in #${chan.name}!`); + message.channel.send(`${discordSuccessEmoji} Enabled NSFW commands in #${chan.name}!`); } Storage.exportDatabase(message.guild.id); break; @@ -109,15 +109,15 @@ module.exports = { case "remove": case "false": db.config.nsfw.allowNSFW = false; - message.channel.send(`${discordConfig.successEmoji} NSFW commands disabled!`); + message.channel.send(`${discordSuccessEmoji} NSFW commands disabled!`); Storage.exportDatabase(message.guild.id); if (!(args[2])) return message.channel.send("Please mention/provide the id of the channel you would like to remove!"); for (let i = 2; i < args.length; i++) { const chan = utilities.parseChannelId(message, args[i]); - if (!chan) return message.channel.send(`${failureEmoji} Channel "${args[i]}" not found...`); - if (!db.config.nsfw.nsfwChannels.includes(chan.id)) return message.channel.send(`${failureEmoji} #${chan.name} is not an NSFW Channel!`); + if (!chan) return message.channel.send(`${discordFailureEmoji} Channel "${args[i]}" not found...`); + if (!db.config.nsfw.nsfwChannels.includes(chan.id)) return message.channel.send(`${discordFailureEmoji} #${chan.name} is not an NSFW Channel!`); db.config.nsfw.nsfwChannels.splice(db.config.nsfw.nsfwChannels.indexOf(chan.id), 1); - message.channel.send(`${successEmoji} Disabled NSFW commands in #${chan.name}!`); + message.channel.send(`${discordSuccessEmoji} Disabled NSFW commands in #${chan.name}!`); } Storage.exportDatabase(message.guild.id); break; @@ -139,10 +139,10 @@ module.exports = { if (!(args[2])) return message.channel.send("Please mention/provide the id of the user you would like to ban!"); for (let i = 2; i < args.length; i++) { const user = utilities.parseUserId(args[i]); - if (!user) return message.channel.send(`${failureEmoji} User "${args[i]}" not found...`); - if (db.config.bannedUsers.includes(user.id)) return message.channel.send(`${failureEmoji} ${user.username}#${user.discriminator} is already banned from using bot commands!`); + if (!user) return message.channel.send(`${discordFailureEmoji} User "${args[i]}" not found...`); + if (db.config.bannedUsers.includes(user.id)) return message.channel.send(`${discordFailureEmoji} ${user.username}#${user.discriminator} is already banned from using bot commands!`); db.config.bannedUsers.push(user.id); - message.channel.send(`${successEmoji} Banned ${user.username}#${user.discriminator} from using commands in this server!`); + message.channel.send(`${discordSuccessEmoji} Banned ${user.username}#${user.discriminator} from using commands in this server!`); } Storage.exportDatabase(message.guild.id); break; @@ -152,10 +152,10 @@ module.exports = { if (!(args[2])) return message.channel.send("Please mention/provide the id of the user you would like to unban!"); for (let i = 2; i < args.length; i++) { const user = utilities.parseUserId(args[i]); - if (!user) return message.channel.send(`${failureEmoji} User "${args[i]}" not found...`); - if (!db.config.bannedUsers.includes(user.id)) return message.channel.send(`${failureEmoji} ${user.username}#${user.discriminator} is not banned from using bot commands!`); + if (!user) return message.channel.send(`${discordFailureEmoji} User "${args[i]}" not found...`); + if (!db.config.bannedUsers.includes(user.id)) return message.channel.send(`${discordFailureEmoji} ${user.username}#${user.discriminator} is not banned from using bot commands!`); db.config.bannedUsers.splice(db.config.bannedUsers.indexOf(user.id), 1); - message.channel.send(`${successEmoji} Un-banned ${user.username}#${user.discriminator} from using commands in this server!`); + message.channel.send(`${discordSuccessEmoji} Un-banned ${user.username}#${user.discriminator} from using commands in this server!`); } Storage.exportDatabase(message.guild.id); break; @@ -176,7 +176,7 @@ module.exports = { }; function noMatch(db, message, dbText, area) { - let buf = "```" + `\nCurrent ${area}:`; + let buf = `\`\`\`\nCurrent ${area}:`; for (let i = 0; i < db.config[dbText].length; i++) { const user = utilities.parseUserId(db.config[dbText][i]); const chan = utilities.parseChannelId(message, db.config[dbText][i]); @@ -191,7 +191,7 @@ function noMatch(db, message, dbText, area) { function nsfwCheck(db, message, dbText, area) { const isNSFW = db.config.nsfw.allowNSFW; - let buf = "```" + `\nCurrent ${area}:`; + let buf = `\`\`\`\nCurrent ${area}:`; for (let i = 0; i < db.config.nsfw[dbText].length; i++) { const chan = utilities.parseChannelId(message, db.config.nsfw[dbText][i]); if (chan) buf += `\n\t${chan.id} (#${chan.name})`; diff --git a/discord/commands/management/starboard.js b/discord/commands/management/starboard.js new file mode 100644 index 0000000..c214bc1 --- /dev/null +++ b/discord/commands/management/starboard.js @@ -0,0 +1,26 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +module.exports = { + desc: "Configures starboard.", + usage: ", , ", + aliases: ["sb", "port"], + isManager: true, + noPm: true, + async process(message, args) { + if (!args[0]) return message.channel.send(`${discordConfig.commandCharacter}starboard ${this.usage}`); + const channel = utilities.parseChannelId(message, args[0]); + if (!channel) return message.channel.send(`${discordFailureEmoji} Unable to parse "${args[0]}" as a channel!"`); + + const db = Storage.getDatabase(message.guild.id); + if (!db.starboard) db.starboard = {}; + db.starboard.channel = channel.id; + db.starboard.requiredStars = parseInt(args[1]) || db.starboard.requiredStars || 3; + db.starboard.emoji = args[2] || db.starboard.emoji || "\u2B50"; + + Storage.exportDatabase(message.guild.id); + + return message.channel.send(`Current Starboard configuration:\nChannel: ${utilities.parseChannelId(message, db.starboard.channel)}\nRequired Stars: ${db.starboard.requiredStars}\nEmoji: ${db.starboard.emoji}`); + }, +}; diff --git a/discord/commands/management/uptime.js b/discord/commands/management/uptime.js index dfd9ca8..b30335d 100644 --- a/discord/commands/management/uptime.js +++ b/discord/commands/management/uptime.js @@ -11,22 +11,22 @@ module.exports = { do { const divisor = divisors.pop(); const unit = uptime % divisor; - buffer.push(unit !== 1 ? unit + " " + units.pop() + "s" : unit + " " + units.pop()); + buffer.push(`${unit} ${units.pop()}${unit !== 1 ? "s" : ""}`); uptime = ~~(uptime / divisor); } while (uptime); switch (buffer.length) { case 5: - text += buffer[4] + ", "; + text += `${buffer[4]}, `; /* falls through */ case 4: - text += buffer[3] + ", "; + text += `${buffer[3]}, `; /* falls through */ case 3: - text += buffer[2] + ", " + buffer[1] + ", and " + buffer[0]; + text += `${buffer[2]}, ${buffer[1]}, and ${buffer[0]}`; break; case 2: - text += buffer[1] + " and " + buffer[0]; + text += `${buffer[1]} and ${buffer[0]}`; break; case 1: text += buffer[0]; diff --git a/discord/commands/ohko.js b/discord/commands/ohko.js new file mode 100644 index 0000000..585bcac --- /dev/null +++ b/discord/commands/ohko.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + desc: "Uses a One-hit KO move.", + async process(message, args) { + return message.channel.send(Tools.random(10) < 3 ? "It's a one-hit KO!" : "The attack missed!"); + }, +}; diff --git a/discord/commands/pick.js b/discord/commands/pick.js index e366311..6135460 100644 --- a/discord/commands/pick.js +++ b/discord/commands/pick.js @@ -6,6 +6,6 @@ module.exports = { aliases: ["choose"], async process(message, args) { if (!(args[0])) return; - return message.channel.send(Tools.sampleOne(args)); + return message.channel.send(`:game_die: I randomly selected... "${Tools.sampleOne(args)}"!`); }, }; diff --git a/discord/commands/points.js b/discord/commands/points.js new file mode 100644 index 0000000..a5ba2ef --- /dev/null +++ b/discord/commands/points.js @@ -0,0 +1,72 @@ +"use strict"; + +const utilities = require("../utilities.js"); + +module.exports = { + usage: "<@user|user id>, <+/- amount (optional)>", + noPm: true, + isElevated: true, + async process(message, args) { + const db = Storage.getDatabase(message.guild.id); + if (!db.points) db.points = {}; + db.points = Object.fromEntries( + Object.entries(db.points).sort(([, a], [, b]) => b - a) + ); + Storage.exportDatabase(message.guild.id); + if (args[0]) args[0] = args[0].trim().split(" ")[0]; + if (!args[0] || !utilities.parseUserId(args[0])) { + // Server Leaderboard + const m = [`Current points leaderboard for ${message.guild.name}: \`\`\``]; + const e = Object.entries(db.points); + let pos = 1; + let last = 0; + for (let i = 0; i < 40; i++) { + if (!e[i] || e[i][1] === 0) break; + const thisUser = utilities.parseUserId(e[i][0]); + if (!thisUser) continue; + if (e[i][1] !== last) pos = i + 1; + m.push(`${pos}) ${e[i][1]} - ${thisUser.username}#${thisUser.discriminator}`); + last = e[i][1]; + } + m.push("```"); + return message.channel.send(m.join("").includes("``````") ? `${m[0]}\nNobody has any points!\`\`\`` : m.join("\n")); + } + const user = utilities.parseUserId(args[0]); + let position = 1; + let last = 0; + let i = 0; + for (const entry of Object.entries(db.points)) { + if (entry[1] !== last) position = i + 1; + last = entry[1]; + i++; + if (entry[0] === user.id) break; + } + if (args[1]) args[1] = args[1].trim().split(" ").join(""); // Clear whitespace + if (!args[1] || isNaN(parseInt(args[1]))) { + // User points + return message.channel.send(`${user.username}#${user.discriminator}'s points: ${db.points[user.id] ? `${db.points[user.id]} | Position: ${position}` : 0}`); + } else { + // Update total + if (!db.points[user.id]) db.points[user.id] = 0; + db.points[user.id] = Math.max(0, db.points[user.id] + parseInt(args[1])); + db.points = Object.fromEntries( + Object.entries(db.points).sort(([, a], [, b]) => b - a) + ); + Storage.exportDatabase(message.guild.id); + let position = 1; + let last = 0; + let i = 0; + for (const entry of Object.entries(db.points)) { + if (entry[1] !== last) position = i + 1; + last = entry[1]; + i++; + if (entry[0] === user.id) break; + } + if (db.points[user.id] === 0) { + return message.channel.send(`${user.username}#${user.discriminator} has no points!`); + } else { + return message.channel.send(`${user.username}#${user.discriminator}'s points: ${db.points[user.id]} | New position: ${position}`); + } + } + }, +}; diff --git a/discord/commands/rng/32to16.js b/discord/commands/rng/32to16.js new file mode 100644 index 0000000..671d478 --- /dev/null +++ b/discord/commands/rng/32to16.js @@ -0,0 +1,20 @@ +"use strict"; + +const LCRNG = require("../../../sources/rng/lcrng.js"); + +module.exports = { + desc: "Gets the nearest 16-bit seed to a provided 32-bit seed. Uses standard GBA LCRNG.", + usage: "<32-bit seed (as Hex)>", + async process(message, args) { + if (isNaN(parseInt(args[0], 16))) return message.channel.send(`Unable to coerce ${args[0]} as a Hex string!`); + args[0] = args[0].toString(16); + const RNG = new LCRNG.PokeRNGR(args[0]); + let result; + let i = 0; + do { + i++; + result = RNG.getNext32BitNumber(); + } while (result >>> 16 !== 0); + return message.channel.send(`Closest 16-bit seed: \`${result}\`, ${i} frame${i !== 1 ? "s" : ""} before ${args[0]}`); + }, +}; diff --git a/discord/commands/rng/naturepair.js b/discord/commands/rng/naturepair.js new file mode 100644 index 0000000..77d4c02 --- /dev/null +++ b/discord/commands/rng/naturepair.js @@ -0,0 +1,68 @@ +"use strict"; + +const LCRNG = require("../../../sources/rng/lcrng.js"); +const RNG = new LCRNG.PokeRNG(); +const RNG2 = new LCRNG.XDRNG(); + +module.exports = { + desc: "Provides the Nature Pair for a given PID", + usage: "", + aliases: ["pair"], + async process(message, args) { + if (!args[0]) return message.channel.send(this.usage); + const pid1 = args[0].toString(16); + const decPid1 = parseInt(pid1, 16); + + if (isNaN(decPid1)) return message.channel.send(`${discordFailureEmoji} Unable to coerce ${args[0]} as a Hex string!`); + if (decPid1 < 0 || decPid1 > 0xFFFFFFFF) return message.channel.send(`${discordFailureEmoji} ${args[0]} is not a valid PID!`); + + const method124Seeds = RNG.calcMethod124SeedIVs(decPid1); + let m1pids = []; + for (let i = 0; i < method124Seeds.length; i++) { + m1pids.push(RNG.concat16([RNG.getNext16BitNumber(RNG.getNext32BitNumber(method124Seeds[i][0])), RNG.getNext16BitNumber(method124Seeds[i][0])])); + method124Seeds[i][0] = (parseInt(method124Seeds[i][0], 16) + 0x80000000) & 0xFFFFFFFF; + m1pids.push(RNG.concat16([RNG.getNext16BitNumber(RNG.getNext32BitNumber(method124Seeds[i][0])), RNG.getNext16BitNumber(method124Seeds[i][0])])); + } + + const methodXDSeeds = RNG2.calcMethodXDSeedIVs(decPid1); + let xdpids = []; + for (let i = 0; i < methodXDSeeds.length; i++) { + xdpids.push(RNG2.concat16([RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodXDSeeds[i][0], 3)), RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodXDSeeds[i][0], 4))])); + methodXDSeeds[i][0] = (parseInt(methodXDSeeds[i][0], 16) + 0x80000000) & 0xFFFFFFFF; + xdpids.push(RNG2.concat16([RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodXDSeeds[i][0], 3)), RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodXDSeeds[i][0], 4))])); + } + + const methodChannelSeeds = RNG2.calcMethodChannelSeedIVs(decPid1); + let channelpids = []; + for (let i = 0; i < methodChannelSeeds.length; i++) { + const part1 = RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodChannelSeeds[i][0], 1)); + const part2 = RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodChannelSeeds[i][0], 2)); + methodChannelSeeds[i][0] = (parseInt(methodChannelSeeds[i][0], 16) + 0x80000000) & 0xFFFFFFFF; + const part3 = RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodChannelSeeds[i][0], 1)); + const part4 = RNG2.getNext16BitNumber(RNG2.getNext32BitNumber(methodChannelSeeds[i][0], 2)); + channelpids.push(RNG2.concat16([part3, part2])); + channelpids.push(RNG2.concat16([part1, part4])); + } + + m1pids = [...new Set(m1pids)]; + xdpids = [...new Set(xdpids)]; + channelpids = [...new Set(channelpids)]; + + const m1natures = []; + for (let i = 0; i < m1pids.length; i += 2) { + m1natures.push(`\n\t• ${RNG.natures[m1pids[i] % 25]} / ${RNG.natures[m1pids[i + 1] % 25]} (${m1pids[i]} / ${m1pids[i + 1]})`); + } + + const xdnatures = []; + for (let i = 0; i < xdpids.length; i += 2) { + xdnatures.push(`\n\t• ${RNG.natures[xdpids[i] % 25]} / ${RNG.natures[xdpids[i + 1] % 25]} (${xdpids[i]} / ${xdpids[i + 1]})`); + } + + const channelnatures = []; + for (let i = 0; i < channelpids.length; i += 2) { + channelnatures.push(`\n\t• ${RNG.natures[channelpids[i] % 25]} / ${RNG.natures[channelpids[i + 1] % 25]} (${channelpids[i]} / ${channelpids[i + 1]})`); + } + + return message.channel.send(`\`\`\`Nature Pairs for PID 0x${args[0].replace(/0x|#/, "").padStart(8, "0")}:\n${m1pids.length > 0 ? `Method 1/2/4:${m1natures.join("")}` : ""}\n${xdpids.length > 0 ? `Colo/XD:${xdnatures.join("")}` : ""}\n${channelpids.length > 0 ? `Channel:${channelnatures.join("")}` : ""}\`\`\``); + }, +}; diff --git a/discord/commands/rng/sidforshiny.js b/discord/commands/rng/sidforshiny.js new file mode 100644 index 0000000..59e6bcc --- /dev/null +++ b/discord/commands/rng/sidforshiny.js @@ -0,0 +1,25 @@ +"use strict"; + +module.exports = { + desc: "Calculates the SID required to make a given PID shiny.", + usage: ", ", + aliases: ["sid4shiny", "square"], + async process(message, args) { + if (!args[1]) return message.channel.send(this.usage); + const decPid = parseInt(args[0], 16); + const decTid = parseInt(args[1]); + + if (isNaN(decPid)) return message.channel.send(`${discordFailureEmoji} Unable to coerce ${args[0]} as a Hex string!`); + if (isNaN(decTid)) return message.channel.send(`${discordFailureEmoji} Unable to coerce ${args[1]} as a decimal!`); + if (decPid < 0 || decPid > 0xFFFFFFFF) return message.channel.send(`${discordFailureEmoji} ${args[0]} is not a valid PID!`); + if (decTid < 0 || decTid > 65535) return message.channel.send(`${discordFailureEmoji} ${args[1]} is not a valid TID!`); + + const pid16High = decPid >>> 16; + const pid16Low = decPid & 0xFFFF; + + const square = pid16High ^ pid16Low ^ decTid; + const rangeLow = 8 * Math.floor(square / 8); + + return message.channel.send(`SID range for shiny: ${rangeLow} - ${rangeLow + 7}\nSID to force square shiny: ${square}`); + }, +}; diff --git a/discord/commands/urbandictionary.js b/discord/commands/urbandictionary.js new file mode 100644 index 0000000..e862ad4 --- /dev/null +++ b/discord/commands/urbandictionary.js @@ -0,0 +1,61 @@ +"use strict"; + +const https = require("https"); + +module.exports = { + desc: "Gets a definition from urban urban dictionary.", + hasCustomFormatting: true, + aliases: ["ud", "urban", "urbandict", "udic"], + usage: "", + async process(message, args) { + const db = Storage.getDatabase(message.guild.id); + if (!args[0]) return message.channel.send(`Usage: \`${discordConfig.commandCharacter}urbandictionary ${this.usage}\``); + try { + const options = { + hostname: "api.urbandictionary.com", + path: `/v0/define?term=${encodeURIComponent(args.join(", ").trim())}`, + method: "GET", + }; + let send = true; + const def = new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + let body = ""; + res.on("data", (res) => { + body += res; + }); + res.on("end", (res) => { + resolve(body); + }); + req.on("error", (err) => { + send = false; + resolve(err); + }); + }); + req.end(); + }); + const data = await def; + const definition = JSON.parse(data.toString()).list[0]; + definition.definition = definition.definition.replace(/\[[a-zA-Z0-9.' ]*\]/gi, replacer); + definition.example = definition.example.replace(/\[[a-zA-Z0-9.' ]*\]/gi, replacer); + const embed = { + title: `"${definition.word}" on Urban Dictionary`, + url: definition.permalink, + fields: [ + { + name: "Example:", + value: definition.example || "(None)", + }, + ], + description: definition.definition || "(None)", + }; + return send ? message.channel.send({embed}) : message.channel.send("Looks like something went wrong..."); + } catch (e) { + message.channel.send("Looks like something went wrong..."); + } + }, +}; + +function replacer(match) { + const words = match.replace(/[[|\]]/g, "").split(" "); + return `[${words.join(" ")}](http://${words.join("-").replace(/[.|']/g, "")}.urbanup.com)`; +} diff --git a/discord/commands/uwuinate.js b/discord/commands/uwuinate.js new file mode 100644 index 0000000..d580dab --- /dev/null +++ b/discord/commands/uwuinate.js @@ -0,0 +1,110 @@ +/************************************************************/ +/* This file is licensed as GNU General Public License v3.0 */ +/************************************************************/ + +"use strict"; + +const kaomojiJoy = [" (* ^ ω ^)", " (o^▽^o)", " (≧◡≦)", " ☆⌒ヽ(*\"、^*)chu", " ( ˘⌣˘)♡(˘⌣˘ )", " xD"]; +const kaomojiEmbarassed = [" (⁄ ⁄>⁄ ▽ ⁄<⁄ ⁄)..", " (*^.^*)..,", "..,", ",,,", "... ", ".. ", " mmm..", "O.o"]; +const kaomojiConfuse = [" (o_O)?", " (°ロ°) !?", " (ーー;)?", " owo?"]; +const kaomojiSparkles = [" *:・゚✧*:・゚✧ ", " ☆*:・゚ ", "〜☆ ", " uguu.., ", "-.-"]; + +module.exports = { + desc: "Converts text to UwU-speak. Powered by https://senguyen1011.github.io/UwUinator/", + usage: "", + aliases: ["uwu", "uwuinator"], + hasCustomFormatting: true, + async process(message, args) { + if (!args[0]) return message.channel.send("uwu"); + const input = args.join(", ").toLowerCase().split(" "); + const output = []; + // from https://github.com/senguyen1011/UwUinator/blob/master/js/uwuinate.js + input.forEach((word, index) => { + let uwu = ""; + + const lastChar = word[word.length - 1]; + let end = ""; + let random = 0; + if (lastChar === "." || lastChar === "?" || lastChar === "!" || lastChar === ",") { + word = word.slice(0, -1); + end = lastChar; + + if (end === ".") { + random = Math.floor(Math.random() * 3); + if (random === 0) { + random = Math.floor(Math.random() * kaomojiJoy.length); + end = kaomojiJoy[random]; + } + } else if (end === "?") { + random = Math.floor(Math.random() * 2); + if (random === 0) { + random = Math.floor(Math.random() * kaomojiConfuse.length); + end = kaomojiConfuse[random]; + } + } else if (end === "!") { + random = Math.floor(Math.random() * 2); + if (random === 0) { + random = Math.floor(Math.random() * kaomojiJoy.length); + end = kaomojiJoy[random]; + } + } else if (end === ",") { + random = Math.floor(Math.random() * 3); + if (random === 0) { + random = Math.floor(Math.random() * kaomojiEmbarassed.length); + end = kaomojiEmbarassed[random]; + } + } + + random = Math.floor(Math.random() * 4); + if (random === 0) { + random = Math.floor(Math.random() * kaomojiSparkles.length); + end = kaomojiSparkles[random]; + } + } + + if (word.indexOf("l") > -1) { + if (word.slice(-2) === "le" || word.slice(-2) === "ll") { + uwu += `${word.slice(0, -2).replace(/l/g, "w").replace(/r/g, "w") + word.slice(-2) + end} `; + } else if (word.slice(-3) === "les" || word.slice(-3) === "lls") { + uwu += `${word.slice(0, -3).replace(/l/g, "w").replace(/r/g, "w") + word.slice(-3) + end} `; + } else { + uwu += `${word.replace(/l/g, "w").replace(/r/g, "w") + end} `; + } + } else if (word.indexOf("r") > -1) { + if (word.slice(-2) === "er" || word.slice(-2) === "re") { + uwu += `${word.slice(0, -2).replace(/r/g, "w") + word.slice(-2) + end} `; + } else if (word.slice(-3) === "ers" || word.slice(-3) === "res") { + uwu += `${word.slice(0, -3).replace(/r/g, "w") + word.slice(-3) + end} `; + } else { + uwu += `${word.replace(/r/g, "w") + end} `; + } + } else { + uwu += `${word + end} `; + } + + uwu = uwu.replace(/you're/g, "ur"); + uwu = uwu.replace(/youre/g, "ur"); + uwu = uwu.replace(/fuck/g, "fwickk"); + uwu = uwu.replace(/shit/g, "poopoo"); + uwu = uwu.replace(/bitch/g, "meanie"); + uwu = uwu.replace(/asshole/g, "b-butthole"); + uwu = uwu.replace(/dick/g, "peenie"); + uwu = uwu.replace(/penis/g, "peenie"); + uwu = uwu.replace(/\bcum\b/g, "cummies"); + uwu = uwu.replace(/\bsemen\b/g, " cummies "); + uwu = uwu.replace(/\bass\b/g, " boi pussy "); + uwu = uwu.replace(/\bdad\b/g, "daddy"); + uwu = uwu.replace(/\bfather\b/g, "daddy"); + + if (uwu.length > 2 && uwu[0].match(/[a-z]/i)) { + random = Math.floor(Math.random() * 6); + if (random === 0) { + uwu = `${uwu[0]}-${uwu}`; + } + } + output.push(uwu); + }); + + return message.channel.send(`\`\`\`${output.join("")}\`\`\``); + }, +}; diff --git a/discord/commands/wiki.js b/discord/commands/wiki.js index e5183d5..36050d9 100644 --- a/discord/commands/wiki.js +++ b/discord/commands/wiki.js @@ -18,7 +18,12 @@ module.exports = { doc = await wtf.fetch(Tools.toTitleCase(args[0])); } if (doc !== null) { - if (doc.json().sections[0].paragraphs[0].sentences) { + if ( + doc.json() && + doc.json().sections && + doc.json().sections[0].paragraphs && + doc.json().sections[0].paragraphs[0].sentences + ) { const desc = []; const index = doc.json().sections[0].paragraphs[0].sentences.length > 0 ? 0 : 1; for (let i = 0; i < 2; i++) { diff --git a/discord/config-example.json b/discord/config-example.json index 6e44898..8b85b71 100644 --- a/discord/config-example.json +++ b/discord/config-example.json @@ -7,5 +7,8 @@ "defaultGen": 8, "successEmoji": "<:greentick:123456789012345678>", "failureEmoji": "<:redcross:123456789012345678>", - "logChannel": "123456789012345678" + "logChannel": "123456789012345678", + "backups": { + "channel": "123456789012345678" + } } diff --git a/discord/editRules.js b/discord/editRules.js new file mode 100644 index 0000000..2e8623f --- /dev/null +++ b/discord/editRules.js @@ -0,0 +1,68 @@ +"use strict"; + +const path = require("path"); + +const Rules = require(path.resolve(__dirname, "./rules.js")); + +const EDIT_RULES_DIRECTORY = path.resolve(__dirname, "./rules/editRules/"); + +class EditRules { + constructor() { + this.rules = []; + } + + async init(isReload) { + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading edit rules...`); + await Promise.all([ + this.loadDirectory(EDIT_RULES_DIRECTORY, Rules.Rule, isReload), + ]); + } + + loadDirectory(directory, Rule, isReload) { + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading ${"Edit".cyan} rules...`); + return new Promise((resolve, reject) => { + fs.readdir(directory, (err, files) => { + if (err) { + reject(`Error reading rules directory: ${err}`); + } else if (!files) { + reject(`No files in directory ${directory}`); + } else { + for (let name of files) { + if (name.endsWith(".js")) { + try { + name = name.slice(0, -3); // remove extention + const rule = new Rule(name, require(`${directory}/${name}.js`)); + this.rules.push(rule); + if (!(isReload)) console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oaded rule ${name.green}`); + } catch (e) { + console.log(`${"Discord: ".yellow + "editRules loadDirectory() error: ".brightRed}${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); + } + } + } + console.log(`${Tools.discordText()}${"Edit".cyan} rules ${isReload ? "rel" : "l"}oaded!`); + resolve(); + } + }); + }); + } + + get(name) { + for (const rule of this.rules) { + if ([rule.name].includes(Tools.toId(name))) return rule; + } + throw new Error(`editRules error: Rule "${name}" not found!`); + } + + process(oldMessage, newMessage) { + for (let i = 0; i < this.rules.length; i++) { + const rule = this.rules[i]; + if (rule.servers.length > 0 && !rule.servers.includes(oldMessage.guild.id)) continue; + if (rule.channels.length > 0 && !rule.channels.includes(oldMessage.channel.id)) continue; + if (rule.users.length > 0 && !rule.users.includes(oldMessage.author.id)) continue; + rule.execute(oldMessage, newMessage); + } + } +} + + +module.exports = EditRules; diff --git a/discord/messageParser.js b/discord/messageParser.js index fc11774..1b76be7 100644 --- a/discord/messageParser.js +++ b/discord/messageParser.js @@ -12,18 +12,18 @@ class MessageParser { } async init(isReload) { - console.log(`${discordText}${isReload ? "Rel" : "L"}oading rules...`); + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading rules...`); await Promise.all([ this.loadDirectory(RULES_DIRECTORY, Rules.Rule, isReload), ]); } loadDirectory(directory, Rule, isReload) { - console.log(`${discordText}${isReload ? "Rel" : "L"}oading ${"Message Parser".cyan} rules...`); + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading ${"Message Parser".cyan} rules...`); return new Promise((resolve, reject) => { fs.readdir(directory, (err, files) => { if (err) { - reject(`Error reading commands directory: ${err}`); + reject(`Error reading rules directory: ${err}`); } else if (!files) { reject(`No files in directory ${directory}`); } else { @@ -31,15 +31,15 @@ class MessageParser { if (name.endsWith(".js")) { try { name = name.slice(0, -3); // remove extention - const rule = new Rule(name, require(directory + "/" + name + ".js")); + const rule = new Rule(name, require(`${directory}/${name}.js`)); this.rules.push(rule); - if (!(isReload)) console.log(`${discordText}${isReload ? "Rel" : "L"}oaded rule ${name.green}`); + if (!(isReload)) console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oaded rule ${name.green}`); } catch (e) { - console.log("Discord: ".yellow + "MessageParser loadDirectory() error: ".brightRed + `${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); + console.log(`${"Discord: ".yellow + "MessageParser loadDirectory() error: ".brightRed}${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); } } } - console.log(`${discordText}${"Message Parser".cyan} rules ${isReload ? "rel" : "l"}oaded!`); + console.log(`${Tools.discordText()}${"Message Parser".cyan} rules ${isReload ? "rel" : "l"}oaded!`); resolve(); } }); @@ -53,14 +53,16 @@ class MessageParser { throw new Error(`messageParser error: Rule "${name}" not found!`); } - process(message) { + async process(message) { for (let i = 0; i < this.rules.length; i++) { const rule = this.rules[i]; + if (message.deleted) break; if (rule.servers.length > 0 && !rule.servers.includes(message.guild.id)) continue; if (rule.channels.length > 0 && !rule.channels.includes(message.channel.id)) continue; if (rule.users.length > 0 && !rule.users.includes(message.author.id)) continue; - rule.execute(message); + message = await rule.execute(message); } + return message; } } diff --git a/discord/plugins/backup.js b/discord/plugins/backup.js new file mode 100644 index 0000000..acdc3cd --- /dev/null +++ b/discord/plugins/backup.js @@ -0,0 +1,123 @@ +"use strict"; + +const archiver = require("archiver"); +const fs = require("fs"); +const path = require("path"); + +const utilities = require("../utilities.js"); + +const databaseDirectory = path.resolve(__dirname, "../../databases"); + +const DEFAULT_TIME = 12 * 60 * 60 * 1000; // 12 hours + +const BACKUP_INTERVAL = discordConfig.backups ? discordConfig.backups.interval ? discordConfig.backups.interval : DEFAULT_TIME : DEFAULT_TIME; + +class Backup { + constructor() { + this.name = "Backup"; + this.disabled = false; + this.timer = null; + } + + async onLoad() { + console.log(`${Tools.discordText()}Loading ${(this.name).cyan} module...`); + this.databases(); + console.log(`${Tools.discordText()}${(this.name).cyan} module loaded!`); + } + + async onEnd() { + if (this.timer) clearInterval(this.timer); + } + + async databases() { + if (discordConfig.backups && await client.channels.cache.get(discordConfig.backups.channel)) { + backupDatabases(); + setInterval(function () { + backupDatabases(); + }, BACKUP_INTERVAL); + } else { + console.log(`${Tools.discordText()}${`${Backup.name} Module: `.cyan}Automatic database backups disabled.`); + } + } +} + +module.exports = new Backup(); + +async function backupDatabases() { + utilities.checkForDb("backup", "{}"); + + const filename = `Database backup - ${new Date().toLocaleString("en-GB").replace(/[/:]/g, "-")}.zip`; + const file = await fs.createWriteStream(`${__dirname}/${filename}`); + const archive = archiver("zip"); + + const message = []; + + Storage.exportDatabases(); + + const backup = Storage.getDatabase("backup"); + + archive.on("error", function (e) { + throw e; + }); + + file.on("close", async function () { + console.log(`${Tools.discordText()}${`${Backup.name} Module: `.cyan}Database backup complete! ${archive.pointer()} total bytes`); + await client.channels.cache.get(discordConfig.backups.channel).send(filename, {files: [`${__dirname}/${filename}`]}); + client.channels.cache.get(discordConfig.backups.channel).send(`Changes: ${message.length > 0 ? `\`\`\`${message.join("\n")}\`\`\`` : "None!"}`); + fs.unlinkSync(`${__dirname}/${filename}`); + }); + + archive.pipe(file); + + await fs.readdir(databaseDirectory, (err, files) => { + for (const file of files) { + const dir = `${databaseDirectory}/${file}`; + archive.append(fs.createReadStream(dir), {name: file}); + + if (file.endsWith(".json") && file !== "backup.json") { + const guild = parseGuildId(file); + const size = fs.statSync(dir).size; + + if (backup[file]) { + if (size > backup[file]) { + if (guild) { + message.push(`${file} (${guild.name}) - ${formatBytes(size)} (+${formatBytes(size - backup[file])})`); + } else { + message.push(`${file} - ${formatBytes(size)} (+${formatBytes(size - backup[file])})`); + } + } + } else { + if (guild) { + message.push(`${file} (${guild.name}) - ${formatBytes(size)} (+${formatBytes(size)})`); + } else { + message.push(`${file} - ${formatBytes(size)} (+${formatBytes(size)})`); + } + } + backup[file] = size; + Storage.exportDatabase("backup"); + } + } + archive.finalize(); + }); +} + +function parseGuildId(input) { + input = input.split(".json")[0]; + if (client.guilds.cache.get(input)) { + return client.guilds.cache.get(input); + } + return undefined; +} + +// From https://stackoverflow.com/a/18650828/13258354 +function formatBytes(bytes, decimals = 2) { + if (bytes === 0) return "0 Bytes"; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; +} diff --git a/discord/reactionEvents.js b/discord/reactionEvents.js new file mode 100644 index 0000000..2894ad3 --- /dev/null +++ b/discord/reactionEvents.js @@ -0,0 +1,19 @@ +"use strict"; + +class Event { + constructor(name, event) { + this.name = name.toLowerCase(); + this.servers = event.servers || []; + this.channels = event.channels || []; + this.users = event.users || []; + this.noPm = event.noPm || false; + this.process = event.process; + this.disabled = event.disabled; + } + + execute(reaction, user, type) { + return this.process(reaction, user, type); + } +} + +module.exports.Event = Event; diff --git a/discord/reactionEvents/add/help.js b/discord/reactionEvents/add/help.js new file mode 100644 index 0000000..9e03fb7 --- /dev/null +++ b/discord/reactionEvents/add/help.js @@ -0,0 +1,68 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +const TIMEOUT = 60 * 1000; + +module.exports = { + async process(reaction, user) { + // Only listen to reactions on messages from bot + if (reaction.message.author.id !== client.user.id) return; + // Only listen to help commands + if (!reaction.message.content.startsWith("moodE Help")) return; + // Only listen to reactions instigated by the original user (stored in the message itself) + const author = utilities.parseUserId(new RegExp("<.*>").exec(reaction.message.content)[0]); + if (author.id === user.id) { + // Pagination wizardry + const pages = reaction.message.content.split(":")[1].match(new RegExp("[0-9]+", "g")); + const page = pages[0]; + const maxPage = pages[1] || 1; + switch (reaction._emoji.name) { + case "\u{23EA}": // ⏪ Rewind Emoji - Go back to start + discordCommandHandler.changeHelpPage(reaction.message, user, 1); + break; + case "\u{25C0}": // ◀️ Back Emoji - Go back one page + discordCommandHandler.changeHelpPage(reaction.message, user, Math.max(1, page - 1)); + break; + case "\u{25B6}": // ▶️ Play Emoji - Go forward one page + discordCommandHandler.changeHelpPage(reaction.message, user, Math.min(parseInt(page) + 1, maxPage)); + break; + case "\u{23E9}": // ⏩ Fast Forward Emoji - Go to end + discordCommandHandler.changeHelpPage(reaction.message, user, maxPage, maxPage); + break; + case "\u{1F522}": // 🔢 1234 Emoji - Prompt for user input and go to that page + const filter = response => { + const num = parseInt(Tools.toId(response.content)); + return response.author.id === user.id && response.channel.id === reaction.message.channel.id && !isNaN(num) && num > 0 && num <= maxPage; + }; + const m = await reaction.message.channel.send(`${user} Please enter the page number that you would like to go to:`); + reaction.message.channel.awaitMessages(filter, {max: 1, time: TIMEOUT, errors: ["time"]}).then(collected => { + discordCommandHandler.changeHelpPage(reaction.message, user, parseInt(Tools.toId(collected.first().content)), maxPage); + collected.first().delete().catch(e => {}); + m.delete().catch(e => {}); + }).catch(collected => { + m.edit(`${discordFailureEmoji} The instruction timed out! Please react again if you still wish to perform this action.`); + }); + break; + case "\u{1F4D8}": // 📘 Blue Book Emoji - Go to Contents (Page 2) + discordCommandHandler.changeHelpPage(reaction.message, user, 2); + break; + } + } + // Purge all reactions that aren't by the bot + const users = await reaction.users.fetch(); + await users.each(async (user) => { + if (user.id !== client.user.id) { + const reactions = reaction.message.reactions.cache.filter(reaction => reaction.users.cache.has(user.id)); + try { + for (const reaction of reactions.values()) { + await reaction.users.remove(user.id); + console.log(`${Tools.discordText()}Removed a reaction by ${user.username}#${user.discriminator} ${`(${user.id})`.grey}`); + } + } catch (e) { + console.log(`${Tools.discordText()}Failed to remove a reaction! ${`(${user.id})`.grey}`); + } + } + }); + }, +}; diff --git a/discord/reactionEvents/add/roles.js b/discord/reactionEvents/add/roles.js new file mode 100644 index 0000000..1e87fe9 --- /dev/null +++ b/discord/reactionEvents/add/roles.js @@ -0,0 +1,37 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +module.exports = { + noPm: true, + async process(reaction, user) { + const db = Storage.getDatabase(reaction.message.guild.id); + if (!db.reactionRoles) return; + for (const messageId of Object.keys(db.reactionRoles)) { + if (messageId === reaction.message.id) { + const emojiName = reaction._emoji.id ? `<:${reaction._emoji.name}:${reaction._emoji.id}>` : reaction._emoji.name.codePointAt(0).toString(16); + const emojis = []; + for (const emoji of Object.keys(db.reactionRoles[messageId])) { + if (emoji.startsWith("<")) { + emojis.push(emoji); + } else { + emojis.push(emoji.codePointAt(0).toString(16)); + } + } + if (emojis.includes(emojiName)) { + const role = await utilities.parseRoleId(reaction.message, db.reactionRoles[messageId][emojiName.startsWith("<") ? emojiName : String.fromCodePoint(`0x${emojiName}`)]); + if (!role) return console.log(`${Tools.discordText()}${`Reaction Role Error`.brightRed}: Unable to retrieve role: ${db.reactionRoles[messageId][emojiName.startsWith("<") ? emojiName : String.fromCodePoint(`0x${emojiName}`)]}`); + const member = await reaction.message.guild.members.fetch(user.id); + if (!member._roles.includes(role.id)) { + try { + await member.roles.add(role); + break; + } catch (e) { + console.log(`${Tools.discordText()}${`Reaction Role Error`.brightRed}: Unable to add ${role.name} ${`(${role.id})`.grey} to ${user.username}#${user.discriminator} ${`(${user.id})`.grey}`); + } + } + } + } + } + }, +}; diff --git a/discord/reactionEvents/add/starboard.js b/discord/reactionEvents/add/starboard.js new file mode 100644 index 0000000..f55918c --- /dev/null +++ b/discord/reactionEvents/add/starboard.js @@ -0,0 +1,73 @@ +"use strict"; + +module.exports = { + noPm: true, + async process(reaction, user) { + const db = Storage.getDatabase(reaction.message.guild.id); + if (!db.starboard) return; + if (!db.starboard.emoji) db.starboard.emoji = "\u{2B50}"; + if (!db.starboard.requiredStars) db.starboard.requiredStars = 3; + if (!db.starboard.roles) db.starboard.roles = []; + if (!db.starboard.stars) db.starboard.stars = {}; + if (!db.starboard.channel) return Storage.exportDatabase(reaction.message.guild.id); + + const emojiName = reaction._emoji.id ? `<:${reaction._emoji.name}:${reaction._emoji.id}>` : reaction._emoji.name; + + if (db.starboard.emoji === emojiName) { + if (db.starboard.roles.length > 0) { + let count = 0; + const users = await reaction.users.fetch(); + await users.each(async (user) => { + if (await reaction.message.guild.members.cache.get(user.id).roles.cache.some(role => db.starboard.roles.includes(role.id))) { + count++; + } + }); + reaction.count = count; + } + if (reaction.count >= db.starboard.requiredStars) { + if (db.starboard.stars[reaction.message.id]) { + // Fetch the message and update the star count + const channel = client.channels.cache.get(db.starboard.channel); + channel.messages.fetch(db.starboard.stars[reaction.message.id]).then(msg => { + let embed = null; + if (msg.embeds && msg.embeds[0]) embed = msg.embeds[0]; + msg.edit(`${reaction.count <= 5 ? (db.starboard.level1 ? db.starboard.level1 : ":star:") : reaction.count <= 10 ? (db.starboard.level2 ? db.starboard.level2 : ":star2:") : (db.starboard.level2 ? db.starboard.level3 : ":stars:")} **${reaction.count}** - ${reaction.message.channel} (${reaction.message.author})`, {embed}); + }); + } else { + const embed = { + color: reaction.message.guild.members.cache.get(reaction.message.author.id).displayColor, + timestamp: new Date(), + author: { + name: `${reaction.message.author.username}`, + icon_url: reaction.message.author.avatarURL(), + }, + fields: [ + { + name: "Link", + value: `[Click me!](${reaction.message.url})`, + inline: true, + }, + ], + footer: { + icon_url: client.user.avatarURL(), + text: "moodE", + }, + }; + const starInfo = `${db.starboard.level1 ? db.starboard.level1 : ":star:"} **${reaction.count}** - ${reaction.message.channel}`; + if (reaction.message.content) { + embed.title = "Message:"; + embed.description = reaction.message.content; + } + if (reaction.message.attachments.first()) { + embed.image = {}; + embed.image.url = reaction.message.attachments.first().url; + } + const sent = await client.channels.cache.get(db.starboard.channel).send(starInfo, {embed}); + await sent.edit(`${starInfo} (${reaction.message.author})`, {embed}); + db.starboard.stars[reaction.message.id] = sent.id; + Storage.exportDatabase(reaction.message.guild.id); + } + } + } + }, +}; diff --git a/discord/reactionEvents/remove/roles.js b/discord/reactionEvents/remove/roles.js new file mode 100644 index 0000000..02db28e --- /dev/null +++ b/discord/reactionEvents/remove/roles.js @@ -0,0 +1,37 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +module.exports = { + noPm: true, + async process(reaction, user) { + const db = Storage.getDatabase(reaction.message.guild.id); + if (!db.reactionRoles) return; + for (const messageId of Object.keys(db.reactionRoles)) { + if (messageId === reaction.message.id) { + const emojiName = reaction._emoji.id ? `<:${reaction._emoji.name}:${reaction._emoji.id}>` : reaction._emoji.name.codePointAt(0).toString(16); + const emojis = []; + for (const emoji of Object.keys(db.reactionRoles[messageId])) { + if (emoji.startsWith("<")) { + emojis.push(emoji); + } else { + emojis.push(emoji.codePointAt(0).toString(16)); + } + } + if (emojis.includes(emojiName)) { + const role = await utilities.parseRoleId(reaction.message, db.reactionRoles[messageId][emojiName.startsWith("<") ? emojiName : String.fromCodePoint(`0x${emojiName}`)]); + if (!role) return console.log(`${Tools.discordText()}${`Reaction Role Error`.brightRed}: Unable to retrieve role: ${db.reactionRoles[messageId][emojiName.startsWith("<") ? emojiName : String.fromCodePoint(`0x${emojiName}`)]}`); + const member = await reaction.message.guild.members.fetch(user.id); + if (member._roles.includes(role.id)) { + try { + await member.roles.remove(role); + break; + } catch (e) { + console.log(`${Tools.discordText()}${`Reaction Role Error`.brightRed}: Unable to remove ${role.name} ${`(${role.id})`.grey} from ${user.username}#${user.discriminator} ${`(${user.id})`.grey}`); + } + } + } + } + } + }, +}; diff --git a/discord/reactionEvents/remove/starboard.js b/discord/reactionEvents/remove/starboard.js new file mode 100644 index 0000000..8961d5b --- /dev/null +++ b/discord/reactionEvents/remove/starboard.js @@ -0,0 +1,48 @@ +"use strict"; + +module.exports = { + noPm: true, + async process(reaction, user) { + const db = Storage.getDatabase(reaction.message.guild.id); + if (!db.starboard) return; + if (!db.starboard.emoji) db.starboard.emoji = "\u{2B50}"; + if (!db.starboard.requiredStars) db.starboard.requiredStars = 3; + if (!db.starboard.roles) db.starboard.roles = []; + if (!db.starboard.stars) db.starboard.stars = {}; + if (!db.starboard.channel) return Storage.exportDatabase(reaction.message.guild.id); + + const emojiName = reaction._emoji.id ? `<:${reaction._emoji.name}:${reaction._emoji.id}>` : reaction._emoji.name; + + if (db.starboard.emoji === emojiName) { + if (db.starboard.roles.length > 0) { + let count = 0; + const users = await reaction.users.fetch(); + await users.each(async (user) => { + if (await reaction.message.guild.members.cache.get(user.id).roles.cache.some(role => db.starboard.roles.includes(role.id))) { + count++; + } + }); + reaction.count = count; + } + if (db.starboard.stars[reaction.message.id]) { + if (reaction.count >= db.starboard.requiredStars) { + const channel = client.channels.cache.get(db.starboard.channel); + channel.messages.fetch(db.starboard.stars[reaction.message.id]).then(msg => { + let embed = null; + if (msg.embeds && msg.embeds[0]) embed = msg.embeds[0]; + msg.edit(`${reaction.count <= 5 ? (db.starboard.level1 ? db.starboard.level1 : ":star:") : reaction.count <= 10 ? (db.starboard.level2 ? db.starboard.level2 : ":star2:") : (db.starboard.level2 ? db.starboard.level3 : ":stars:")} **${reaction.count}** - ${reaction.message.channel} (${reaction.message.author})`, {embed}); + }); + } else { + const channel = client.channels.cache.get(db.starboard.channel); + channel.messages.fetch(db.starboard.stars[reaction.message.id]).then(msg => { + try { + msg.delete(); + delete db.starboard.stars[reaction.message.id]; + Storage.exportDatabase(reaction.message.guild.id); + } catch (e) {} + }); + } + } + } + }, +}; diff --git a/discord/reactionHandler.js b/discord/reactionHandler.js new file mode 100644 index 0000000..feb4cd1 --- /dev/null +++ b/discord/reactionHandler.js @@ -0,0 +1,80 @@ +"use strict"; + +const path = require("path"); + +const ReactionEvent = require(path.resolve(__dirname, "./reactionEvents.js")); + +const REACTION_ADD_EVENTS_DIRECTORY = path.resolve(__dirname, "./reactionEvents/add"); +const REACTION_REMOVE_EVENTS_DIRECTORY = path.resolve(__dirname, "./reactionEvents/remove"); + +class ReactionEvents { + constructor() { + this.add = []; + this.remove = []; + } + + async init(isReload) { + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading reaction events...`); + await Promise.all([ + this.loadDirectory(REACTION_ADD_EVENTS_DIRECTORY, ReactionEvent.Event, "Add", isReload), + this.loadDirectory(REACTION_REMOVE_EVENTS_DIRECTORY, ReactionEvent.Event, "Remove", isReload), + ]); + } + + loadDirectory(directory, Event, type, isReload) { + console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oading ${`Reaction ${type}`.cyan} events...`); + return new Promise((resolve, reject) => { + fs.readdir(directory, (err, files) => { + if (err) { + reject(console.log(`Error reading reaction events directory: ${err}`)); + } else if (!files) { + reject(console.log(`No files in directory ${directory}`)); + } else { + for (let name of files) { + if (name.endsWith(".js")) { + try { + name = name.slice(0, -3); // remove extention + const event = new Event(name, require(`${directory}/${name}.js`)); + if (type === "Add") { + this.add.push(event); + } else { + this.remove.push(event); + } + if (!(isReload)) console.log(`${Tools.discordText()}${isReload ? "Rel" : "L"}oaded reaction event ${name.green}`); + } catch (e) { + console.log(`${Tools.discordText()}${"reactionHandler loadDirectory() error: ".brightRed}${e.stack} while parsing ${name.yellow}${".js".yellow} in ${directory}`); + } + } + } + console.log(`${Tools.discordText()}${`Reaction ${type}`.cyan} events ${isReload ? "rel" : "l"}oaded!`); + resolve(); + } + }); + }); + } + + process(reaction, user, type) { + if (type === "Add") { + for (let i = 0; i < this.add.length; i++) { + const event = this.add[i]; + if (event.noPm && reaction.message.channel.type === "dm") continue; + if (event.servers.length > 0 && !event.servers.includes(reaction.message.guild.id)) continue; + if (event.channels.length > 0 && !event.channels.includes(reaction.message.channel.id)) continue; + if (event.users.length > 0 && !event.users.includes(user.id)) continue; + event.execute(reaction, user); + } + } else { + for (let i = 0; i < this.remove.length; i++) { + const event = this.remove[i]; + if (event.noPm && reaction.message.channel.type === "dm") continue; + if (event.servers.length > 0 && !event.servers.includes(reaction.message.guild.id)) continue; + if (event.channels.length > 0 && !event.channels.includes(reaction.message.channel.id)) continue; + if (event.users.length > 0 && !event.users.includes(user.id)) continue; + event.execute(reaction, user); + } + } + } +} + + +module.exports = ReactionEvents; diff --git a/discord/rules.js b/discord/rules.js index cc712f8..0d259da 100644 --- a/discord/rules.js +++ b/discord/rules.js @@ -9,8 +9,10 @@ class Rule { this.process = rule.process; } - execute(message) { - return this.process(message); + // oldMessage will be run as `message` in MessageParser rules, but this way we + // Can use the same framework for editRules too + execute(oldMessage, newMessage) { + return this.process(oldMessage, newMessage); } } diff --git a/discord/rules/editRules/editRule.js.example b/discord/rules/editRules/editRule.js.example new file mode 100644 index 0000000..9db6fd9 --- /dev/null +++ b/discord/rules/editRules/editRule.js.example @@ -0,0 +1,14 @@ +"use strict"; + +const utilities = require("../../utilities.js"); + +module.exports = { + servers: ["array", "of", "IDs"], // Rule will only be run in these servers + channels: ["array", "of", "channels"], // Rule will only be run in these channels + users: ["array", "of", "users"], // Rule will only be run on messages authored by one of the provided users + async process(oldMessage, newMessage) { + // Prevent the rule from running if the message looks like a command + // Remove this next line if you want to rule to run on commands as well + if (Tools.toId(newMessage.content).includes("chicken") && utilities.oneIn(10)) return newMessage.channel.send("Did somebody say... CHICKEN!??!"); + }, +}; diff --git a/discord/rules/editRules/filter.js b/discord/rules/editRules/filter.js new file mode 100644 index 0000000..8993d8e --- /dev/null +++ b/discord/rules/editRules/filter.js @@ -0,0 +1,60 @@ +"use strict"; + +const glyph = require("../../../sources/homoglyph.js"); +const removeDiacritics = require("diacritics").remove; + +const KAOMOJI = ["(* ^ ω ^)", "(o^▽^o)", "(≧◡≦)", "☆⌒ヽ(*\"、^*)chu", "( ˘⌣˘)♡(˘⌣˘ )", "(눈_눈)", "ʕ º ᴥ ºʔ"]; + +module.exports = { + servers: ["614615934979801113"], + async process(oldMessage, newMessage) { + if (newMessage.author.bot) return; + const db = Storage.getDatabase(newMessage.guild.id); + if (!db.filter || db.filter.length === 0) return; + + const member = await client.guilds.cache.get(newMessage.guild.id).members.cache.get(newMessage.author.id); + // Mods should be immune + if (member.hasPermission("MANAGE_MESSAGES")) return; + + const banwordRegex = new RegExp(`(?:\\b|(?!\\w))(?:${glyph.replace(removeDiacritics(db.filter.join("?"))).toLowerCase().replace(/i/g, "[i|l]").replace(/\?/g, "|")})(?:\\b|\\B(?!\\w))`, "i"); + + // Check for filtered words in the newMessage + for (const word of glyph.replace(removeDiacritics(newMessage.content).replace(/[.]/g, "")).toLowerCase().split(" ")) { + if (banwordRegex.test(word)) { + try { + await newMessage.delete(); + newMessage.channel.send(`${discordFailureEmoji} ${newMessage.author}, your essage contained terms that are not permitted in this server, and has been deleted.`); + } catch (e) {} + } + } + + // Check for filtered words in user nickname + let inapNickname = false; + if (member.nickname) { + for (const word of glyph.replace(removeDiacritics(member.nickname.replace(/[.]/g, ""))).toLowerCase().split(" ")) { + if (banwordRegex.test(word)) { + try { + await member.setNickname(""); + inapNickname = true; + } catch (e) {} + } + } + } + + // Check for filtered words in username + let inapUsername = false; + const currentMember = await client.guilds.cache.get(newMessage.guild.id).members.cache.get(newMessage.author.id); + if (!currentMember.nickname) { + for (const word of glyph.replace(removeDiacritics(newMessage.author.username.replace(/[.]/g, ""))).toLowerCase().split(" ")) { + if (banwordRegex.test(word)) { + try { + await member.setNickname(Tools.sampleOne(KAOMOJI)); + inapUsername = true; + } catch (e) {} + } + } + } + + if (inapUsername || inapNickname) newMessage.channel.send(`${discordFailureEmoji} ${newMessage.author}, your user/nickname contained terms that are not permitted in this server, so your nickname has been reset.`); + }, +}; diff --git a/discord/rules/filter.js b/discord/rules/filter.js new file mode 100644 index 0000000..b577444 --- /dev/null +++ b/discord/rules/filter.js @@ -0,0 +1,102 @@ +"use strict"; + +const glyph = require("../../sources/homoglyph.js"); +const removeDiacritics = require("diacritics").remove; + +const KAOMOJI = ["(* ^ ω ^)", "(o^▽^o)", "(≧◡≦)", "☆⌒ヽ(*\"、^*)chu", "( ˘⌣˘)♡(˘⌣˘ )", "(눈_눈)", "ʕ º ᴥ ºʔ"]; + +module.exports = { + async process(message) { + if (message.author.bot) return message; + const db = Storage.getDatabase(message.guild.id); + if (!db.filter || db.filter.length === 0) return message; + + const member = await client.guilds.cache.get(message.guild.id).members.cache.get(message.author.id); + // Mods should be immune + if (member.hasPermission("MANAGE_MESSAGES") || discordConfig.admin.includes(message.author.id)) return message; + + const filterWords = []; + for (const word of db.filter) { + const newWord = []; + const openIndexes = []; + const closeIndexes = []; + let lastChar = ""; + let i = 0; + for (const char of word) { + if (char === "[" && lastChar !== "\\") openIndexes.push(i); + if (char === "]" && lastChar !== "\\") closeIndexes.push(i); + lastChar = char; + i++; + } + i = 0; + let j = 0; + for (let char of word) { + if (i > openIndexes[j] && i < closeIndexes[j]) { + if (char === "a" || char === "d") char = "a|d"; + if (char === "i" || char === "l" || char === "j") char = "i|\||l|j"; // eslint-disable-line + if (char === "n" || char === "x") char = "n|x"; + newWord.push(char); + } else { + if (char === "a" || char === "d") char = "[ad]"; + if (char === "i" || char === "l" || char === "j") char = "[i|lj]"; + if (char === "n" || char === "x") char = "[nx]"; + newWord.push(char); + } + if (char === "]") j++; + i++; + } + filterWords.push(newWord.join("")); + } + + const banwordRegex = new RegExp(`(?:\\b|(?!\\w))(?:${filterWords.join("<<>>").toLowerCase().replace(/<<>>/g, "|")/* .replace(/[ilj]/g, "[i|lj]").replace(/[ad]/g, "[ad]").replace(/[nx]/g, "[nx]").replace(/<<>>/g, "|")}*/})(?:\\b|\\B(?!\\w))`, "i"); + + // Check for filtered words in the message + for (const word of glyph.replace(removeDiacritics(message.content).replace(/[.]/g, "")).toLowerCase().split(" ")) { + if (banwordRegex.test(word)) { + try { + await message.delete(); + message.deleted = true; + message.channel.send(`${discordFailureEmoji} ${message.author}, your message contained terms that are not permitted in this server, and has been deleted.`); + break; + } catch (e) { + console.log(e); + } + } + } + + // Check for filtered words in user nickname + let inapNickname = false; + if (member.nickname) { + for (const word of glyph.replace(removeDiacritics(member.nickname.replace(/[.]/g, ""))).toLowerCase().split(" ")) { + if (banwordRegex.test(word)) { + try { + await member.setNickname(""); + inapNickname = true; + } catch (e) { + console.log(e); + } + } + } + } + + // Check for filtered words in username + let inapUsername = false; + const currentMember = await client.guilds.cache.get(message.guild.id).members.cache.get(message.author.id); + if (!currentMember.nickname) { + for (const word of glyph.replace(removeDiacritics(message.author.username.replace(/[.]/g, ""))).toLowerCase().split(" ")) { + if (banwordRegex.test(word)) { + try { + await member.setNickname(Tools.sampleOne(KAOMOJI)); + inapUsername = true; + } catch (e) { + console.log(e); + } + } + } + } + + if (inapUsername || inapNickname) message.channel.send(`${discordFailureEmoji} ${message.author}, your user/nickname contained terms that are not permitted in this server, so your nickname has been reset.`); + + return message; + }, +}; diff --git a/discord/rules/get-ps-battle-info.js b/discord/rules/get-ps-battle-info.js new file mode 100644 index 0000000..d49e747 --- /dev/null +++ b/discord/rules/get-ps-battle-info.js @@ -0,0 +1,34 @@ +"use strict"; + +const sleep = require("system-sleep"); + +module.exports = { + async process(message) { + // Prevent the rule from running if the message looks like a command + // Remove this next line if you want to rule to run on commands as well + if (message.content.startsWith(discordConfig.commandCharacter)) return message; + if (!message.content.includes("://play.pokemonshowdown.com/battle-") || !runShowdown) return message; + + const match = /https?:\/\/play\.pokemonshowdown\.com\/battle-(.+)-(\d+)/g.exec(message.content); + if (!match) return message; + match.shift(); + const [tier, match_id] = match; + const battle_path = `battle-${tier}-${match_id}`; + + for (let i = 0; i < 10; i++) { + psClient.send(`|/join ${battle_path}`); + await sleep(500); + if (psRooms.get(battle_path) !== undefined) break; + } + + const room = psRooms.get(battle_path); + if (room) { + message.channel.send(`${room.title} || ${room.tier}`); + room.say("/part"); + } else { + message.channel.send("Unable to find info on this battle."); + } + + return message; + }, +}; diff --git a/discord/rules/link-message.js b/discord/rules/link-message.js index c3a82d3..2ab6d8f 100644 --- a/discord/rules/link-message.js +++ b/discord/rules/link-message.js @@ -5,7 +5,8 @@ module.exports = { channels: [], users: [], async process(message) { - const regex = /https:\/\/discordapp\.com\/channels\/[0-9]*\/[0-9]*\/[0-9]*/g; + if (message.author.bot) return message; + const regex = /https:\/\/discord(|app)\.com\/channels\/[0-9]*\/[0-9]*\/[0-9]*/g; const matches = [...new Set(message.content.match(regex))]; // Remove duplicate entries for (let match of matches) { match = match.split("/"); @@ -29,9 +30,13 @@ module.exports = { } const author = `${msg.author.username}#${msg.author.discriminator}`; - return message.channel.send(`Message from ${author}, posted in ${channel} on ${new Date(msg.createdTimestamp).toUTCString()}${attachmentNum > 0 ? " (with " + attachmentNum + " attachment" + (attachmentNum === 1 ? "" : "s") + ")" : ""}:\n${msg.content}`, {files: attachments}); + message.channel.send(`Message from ${author}, posted in ${channel} on ${new Date(msg.createdTimestamp).toUTCString()}${attachmentNum > 0 ? ` (with ${attachmentNum} attachment${attachmentNum === 1 ? "" : "s"})` : ""}:\n${msg.content ? `>>> ${msg.content}` : ""}`, {files: attachments}); + return message; }); - } catch (e) {} + } catch (e) { + return message; + } } + return message; }, }; diff --git a/discord/rules/rule.js.example b/discord/rules/rule.js.example index 40be2b4..4df79e1 100644 --- a/discord/rules/rule.js.example +++ b/discord/rules/rule.js.example @@ -9,7 +9,8 @@ module.exports = { async process(message) { // Prevent the rule from running if the message looks like a command // Remove this next line if you want to rule to run on commands as well - if (message.content.startsWith(discordConfig.commandCharacter)) return; - if (Tools.toId(message.content).includes("chicken") && utilities.oneIn(10)) return message.channel.send("Did somebody say... CHICKEN!??!"); + if (message.content.startsWith(discordConfig.commandCharacter)) return message; + if (Tools.toId(message.content).includes("chicken") && utilities.oneIn(10)) message.channel.send("Did somebody say... CHICKEN!??!"); + return message; }, }; diff --git a/discord/utilities.js b/discord/utilities.js index 2c2b83b..9c208c9 100644 --- a/discord/utilities.js +++ b/discord/utilities.js @@ -23,7 +23,7 @@ class Utilities { if (!(db.config.commands[cmd])) db.config.commands[cmd] = {"uses": {"total": 0, "users": {}}, "requiredRoles": [], "bannedUsers": [], "bannedChannels": [], "isElevated": false, "isManager": false}; if (!(db.config.commands[cmd].uses.users[message.author.id])) db.config.commands[cmd].uses.users[message.author.id] = {}; - db.config.commands[cmd].uses.users[message.author.id].name = message.author.username + "#" + message.author.discriminator; // Update identifier in case Username has changed since last time + db.config.commands[cmd].uses.users[message.author.id].name = `${message.author.username}#${message.author.discriminator}`; // Update identifier in case Username has changed since last time if (!(db.config.commands[cmd].uses.users[message.author.id].times)) db.config.commands[cmd].uses.users[message.author.id].times = 0; db.config.commands[cmd].uses.total += 1; db.config.commands[cmd].uses.users[message.author.id].times += 1; @@ -33,7 +33,7 @@ class Utilities { } buildDb(id, name) { - console.log(`${discordText}Building database for ${name.green}...`); + console.log(`${Tools.discordText()}Building database for ${name.green}...`); const db = Storage.getDatabase(id); db.name = name; // Update identifier in case the server has changed name since the last command if (!(db.config.nsfw)) db.config.nsfw = {"allowNSFW": false, "nsfwChannels": []}; @@ -48,14 +48,14 @@ class Utilities { const db = Storage.getDatabase(id); if (!(db.config.commands)) db.config.commands = {}; if (!(db.config.commands[cmd])) { - console.log(`${discordText}Adding ${type !== "NSFW" ? cmd.green : cmd.charAt(0).green + "*****".green} to ${(client.guilds.cache.get(id).name).cyan} database...`); + console.log(`${Tools.discordText()}Adding ${type !== "NSFW" ? cmd.green : cmd.charAt(0).green + "*****".green} to ${(client.guilds.cache.get(id).name).cyan} database...`); db.config.commands[cmd] = {"uses": {"total": 0, "users": {}}, "requiredRoles": [], "bannedUsers": [], "bannedChannels": [], "isElevated": false, "isManager": false}; } Storage.exportDatabase(id); } generateRandomLinkCode(len) { - const length = len > 0 ? len : 4; + const length = len > 0 ? len : 8; if (!(fs.existsSync(path.resolve(__dirname, "../databases/linkCodes.json")))) { fs.writeFileSync(path.resolve(__dirname, "../databases/linkCodes.json"), `{"linkCodes":[]}`); Storage.importDatabase("linkCodes"); @@ -85,8 +85,10 @@ class Utilities { } parseRoleId(message, input) { + if (!message) throw new Error("parseRoleId() requires a message object!"); + if (!input) throw new Error("parseRoleId() requires a role id!"); if (input.includes("<")) { - return message.guild.roles.get(input.match(/^<@&?(\d+)>$/)[1]); + return message.guild.roles.cache.get(input.match(/^<@&?(\d+)>$/)[1]); } else { let name = message.guild.roles.cache.find(r => Tools.toId(r.name) === Tools.toId(input)); if (!name) name = message.guild.roles.cache.get(input); @@ -95,6 +97,8 @@ class Utilities { } parseChannelId(message, input) { + if (!message) throw new Error("parseChannelId() requires a message object!"); + if (!input) throw new Error("parseChannelId() requires a channel id!"); if (input.includes("<")) { return message.guild.channels.cache.get(input.match(/^<#?(\d+)>$/)[1]); } else { @@ -104,12 +108,37 @@ class Utilities { } } + + async parseMessageId(message, input) { + if (!message) throw new Error("parseMessageId() requires a message object!"); + if (!input) throw new Error("parseMessageId() requires a message id!"); + let msg; + for (const channel of message.guild.channels.cache) { + if (!channel[1].messages) continue; + msg = await channel[1].messages.fetch(input).catch(e => { + msg = undefined; + }); + if (msg) break; + } + if (!msg) throw new Error(`No message ${input} found on this guild!`); + return msg; + } + oneIn(number) { const rand = Tools.random(number); if (rand === 0) return true; return false; } + getFc(str) { + const db = Storage.getDatabase("fc").fc; + for (const id of Object.keys(db)) { + const entry = db[id]; + if ((this.parseUserId(str) && this.parseUserId(str).id === id) || str.replace("@", "") === entry.user) return entry; + } + return "No FCs found."; + } + toSmogonString(dex) { let genStr = "ss"; switch (dex) { diff --git a/moode.js b/moode.js index faa42ad..a69785d 100644 --- a/moode.js +++ b/moode.js @@ -3,11 +3,14 @@ // define constants const child_process = require("child_process"); const Discord = require("discord.js"); +const Twitch = require("tmi.js"); const util = require("util"); const exec = util.promisify(child_process.exec); -const runDiscord = true; +global.runDiscord = true; +global.runShowdown = true; +global.runTwitch = true; // set up globals global.colors = require("colors"); @@ -17,15 +20,20 @@ global.path = require("path"); global.Storage = require("./sources/storage.js"); global.Tools = require("./sources/tools.js"); -global.discordText = "Discord-Bot: ".yellow; -global.moodeText = "moodE: ".yellow; -global.showdownText = "pokemon-showdown: ".yellow; - Storage.importDatabases(); // Modified from https://github.com/sirDonovan/Lanette/blob/master/build.js (async (resolve, reject) => { - console.log(`${moodeText}Checking pokemon-showdown remote...`); + // Print welcome message + console.log(`${Tools.moodeText()}${"-------------------------------------".yellow}`); + console.log(`${Tools.moodeText()}${"| Welcome to moodE! |".yellow}`); + console.log(`${Tools.moodeText()}${"-------------------------------------".yellow}`); + console.log(Tools.moodeText()); + + const moodeHash = await exec("git rev-parse --short HEAD"); + global.hash = moodeHash.stdout.trim(); + + console.log(`${Tools.moodeText()}Checking pokemon-showdown remote...`); const pokemonShowdown = path.join(__dirname, "pokemon-showdown"); const moodeRemote = "https://github.com/smogon/pokemon-showdown.git"; const moodeShaDir = path.join(__dirname, "pokemon-showdown.sha"); @@ -40,15 +48,15 @@ Storage.importDatabases(); } if (!(fs.existsSync(moodeShaDir))) { - console.log(`${moodeText}Creating pokemon-showdown.sha...`); - fs.writeFileSync(moodeShaDir); + console.log(`${Tools.moodeText()}Creating pokemon-showdown.sha...`); + fs.writeFileSync(moodeShaDir, " "); } process.chdir(pokemonShowdown); const remoteOutput = await exec("git remote -v").catch(e => console.log(e)); if (!remoteOutput || remoteOutput.Error) { - console.log(`${moodeText}${"Error".red}: No git remote output.`); + console.log(`${Tools.moodeText()}${"Error".red}: No git remote output.`); reject(); return; } @@ -68,22 +76,22 @@ Storage.importDatabases(); if (!currentRemote || currentRemote.trim() !== moodeRemote.trim()) { needsClone = true; deleteFolderRecursive(pokemonShowdown); - if (!firstRun) console.log(`${moodeText}Deleted old remote ${currentRemote}`); + if (!firstRun) console.log(`${Tools.moodeText()}Deleted old remote ${currentRemote}`); } if (needsClone) { const hrStart = process.hrtime(); - console.log(`${moodeText}Cloning ${moodeRemote.trim().cyan} (This may take some time!)`); - const cmd = await exec("git clone " + moodeRemote).catch(e => console.log(e)); + console.log(`${Tools.moodeText()}Cloning ${moodeRemote.trim().cyan} (This may take some time!)`); + const cmd = await exec(`git clone ${moodeRemote}`).catch(e => console.log(e)); if (!cmd || cmd.Error) { reject(); return; } const hrEnd = process.hrtime(hrStart); const timeString = `${Math.floor(hrEnd[0] / 60)} min ${hrEnd[0] % 60} sec`; - console.log(`${moodeText}Cloned into ${pokemonShowdown.cyan} ${("(" + timeString + ")").grey}`); + console.log(`${Tools.moodeText()}Cloned into ${pokemonShowdown.cyan} ${(`(${timeString})`).grey}`); } else { - console.log(`${moodeText}No clone required!`); + console.log(`${Tools.moodeText()}No clone required!`); } process.chdir(pokemonShowdown); @@ -96,52 +104,85 @@ Storage.importDatabases(); const moodeSha = fs.readFileSync(moodeShaDir).toString().trim(); const currentSha = revParseOutput.stdout.replace("\n", ""); if (moodeSha !== currentSha) { - console.log(`${moodeText}Writing sha... ${("(" + currentSha + ")").grey}`); + console.log(`${Tools.moodeText()}Writing sha... ${(`(${currentSha})`).grey}`); fs.writeFileSync(moodeShaDir, currentSha); } - console.log(`${showdownText}Attempting pull...`); + console.log(`${Tools.pokemonShowdownText()}Attempting pull...`); const pull = await exec("git pull"); if (!pull || pull.Error) { needsBuild = false; - console.log(`${showdownText}Error: could not pull origin.`); + console.log(`${Tools.pokemonShowdownText()}Error: could not pull origin.`); return; } if (pull.stdout.replace("\n", "").replace(/-/g, " ") === "Already up to date.") { needsBuild = false; - console.log(`${showdownText}Already up to date!`); + console.log(`${Tools.pokemonShowdownText()}Already up to date!`); } else { - console.log(`${showdownText}Pull completed!`); + console.log(`${Tools.pokemonShowdownText()}Pull completed!`); } if (firstRun || needsBuild || needsClone) { - console.log(`${showdownText}Commencing build script...`); + console.log(`${Tools.pokemonShowdownText()}Commencing build script...`); await exec("node build").catch(e => console.log(e)); - console.log(`${showdownText}Built!`); + console.log(`${Tools.pokemonShowdownText()}Built!`); } process.chdir(__dirname); if (runDiscord) { - console.log(`${moodeText}Launching Discord...`); + console.log(`${Tools.moodeText()}Launching Discord...`); try { fs.accessSync(path.resolve(__dirname, "./discord/config.json")); } catch (e) { if (e.code !== "ENOENT") throw e; - console.log(`${discordText}: No discord configuration file found...`); - console.log(`${discordText}: Writing one with default values. Please fill it out with your own information!`); + console.log(`${Tools.discordText()}No discord configuration file found...`); + console.log(`${Tools.discordText()}Writing one with default values. Please fill it out with your own information!`); fs.writeFileSync(path.resolve(__dirname, "./discord/config.json"), fs.readFileSync(path.resolve(__dirname, "./discord/config-example.json"))); } - global.client = new Discord.Client(); + global.client = new Discord.Client({partials: ["MESSAGE", "CHANNEL", "REACTION"]}); global.discordConfig = require("./discord/config.json"); - global.successEmoji = discordConfig.successEmoji || "\u2705"; - global.failureEmoji = discordConfig.failureEmoji || "\u274C"; + global.discordSuccessEmoji = discordConfig.successEmoji || "\u2705"; + global.discordFailureEmoji = discordConfig.failureEmoji || "\u274C"; if (!discordConfig.token) throw new Error(`${discordText}Please specify a token in config.json!`); global.discord = require("./discord/app.js"); } else { - console.log(`${moodeText}Discord disabled.`); + console.log(`${Tools.moodeText()}Discord Bot disabled.`); + console.log(`${Tools.moodeText()}${"/!\\".yellow}PLEASE NOTE THAT THIS ALSO DISABLES DATABASE BACKUPS!!! ${"/!\\".yellow}`); + } + + if (runShowdown) { + console.log(`${Tools.moodeText()}Launching PS Bot...`); + try { + fs.accessSync(path.resolve(__dirname, "./showdown/config.json")); + } catch (e) { + if (e.code !== "ENOENT") throw e; + console.log(`${Tools.showdownText()}No PS Bot configuration file found...`); + console.log(`${Tools.showdownText()}Writing one with default values. Please fill it out with your own information!`); + fs.writeFileSync(path.resolve(__dirname, "./showdown/config.json"), fs.readFileSync(path.resolve(__dirname, "./showdown/config-example.json"))); + } + global.psBot = require("./showdown/app.js"); + } else { + console.log(`${Tools.moodeText()}PS Bot disabled.`); + } + + if (runTwitch) { + console.log(`${Tools.moodeText()}Launching Twitch Bot...`); + try { + fs.accessSync(path.resolve(__dirname, "./twitch/config.json")); + } catch (e) { + if (e.code !== "ENOENT") throw e; + console.log(`${Tools.twitchText()}No Twitch Bot configuration file found...`); + console.log(`${Tools.twitchText()}Writing one with default values. Please fill it out with your own information!`); + fs.writeFileSync(path.resolve(__dirname, "./twitch/config.json"), fs.readFileSync(path.resolve(__dirname, "./twitch/config-example.json"))); + } + global.twitchConfig = require("./twitch/config.json"); + global.bot = new Twitch.Client(twitchConfig); + global.twitch = require("./twitch/app.js"); + } else { + console.log(`${Tools.moodeText()}Twitch Bot disabled.`); } })(); diff --git a/package-lock.json b/package-lock.json index fd518ec..fbed8a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,48 +1,2445 @@ { "name": "moode-rewrite", - "version": "2.0.0", - "lockfileVersion": 1, + "version": "2.6.0", + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "moode-rewrite", + "version": "2.6.0", + "license": "MIT", + "dependencies": { + "archiver": "^5.3.0", + "colors": "^1.4.0", + "convert-units": "^2.3.4", + "diacritics": "^1.3.0", + "discord.js": "^12.5.3", + "hex2dec": "^1.1.2", + "homoglyph-search": "^1.2.1", + "node-html-to-image": "^3.2.0", + "probe-image-size": "^7.2.1", + "system-sleep": "^1.3.7", + "tmi.js": "^1.8.3", + "websocket": "^1.0.34", + "wtf_wikipedia": "^8.5.1" + }, + "devDependencies": { + "eslint": "^7.29.0", + "husky": "^6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@discordjs/collection": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", + "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" + }, + "node_modules/@discordjs/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@types/node": { + "version": "15.12.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.5.tgz", + "integrity": "sha512-se3yX7UHv5Bscf8f1ERKvQOD6sTyycH3hdaoozvaLxgUiY5lIGEeH37AD0G0Qi9kPqihPn0HOfd2yaIEN9VwEg==", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "engines": { + "node": "*" + } + }, + "node_modules/bufferutil": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", + "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", + "dependencies": { + "node-gyp-build": "^4.2.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/convert-units": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/convert-units/-/convert-units-2.3.4.tgz", + "integrity": "sha512-ERHfdA0UhHJp1IpwE6PnFJx8LqG7B1ZjJ20UvVCmopEnVCfER68Tbe3kvN63dLbYXDA2xFWRE6zd4Wsf0w7POg==", + "dependencies": { + "lodash.foreach": "2.3.x", + "lodash.keys": "2.3.x" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/deasync": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.21.tgz", + "integrity": "sha512-kUmM8Y+PZpMpQ+B4AuOW9k2Pfx/mSupJtxOsLzmnHY2WqZUYRFccFn2RhzPAqt3Xb+sorK/badW2D4zNzqZz5w==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^1.7.1" + }, + "engines": { + "node": ">=0.11.0" + } + }, + "node_modules/deasync-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deasync-promise/-/deasync-promise-1.0.1.tgz", + "integrity": "sha1-KyfeR4Fnr07zS6mYecUuwM7dYcI=", + "dependencies": { + "deasync": "^0.1.7" + } + }, + "node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diacritics": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", + "integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E=" + }, + "node_modules/discord.js": { + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", + "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", + "dependencies": { + "@discordjs/collection": "^0.1.6", + "@discordjs/form-data": "^3.0.1", + "abort-controller": "^3.0.0", + "node-fetch": "^2.6.1", + "prism-media": "^1.2.9", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.4.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dependencies": { + "type": "^2.0.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hex2dec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.2.tgz", + "integrity": "sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA==" + }, + "node_modules/homoglyph-search": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/homoglyph-search/-/homoglyph-search-1.2.1.tgz", + "integrity": "sha512-ykghjGrEUo3oMw88fiNBfbsip18XYg2IgQyN/esf9Fy0qNRY9FHBpWKzMNT0pDJ8img4WHdUqszQifFSb/JtrA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/husky": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", + "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lodash._basebind": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.3.0.tgz", + "integrity": "sha1-K1vEUqDhBhQ7IYafIzvbWHQX0kg=", + "dependencies": { + "lodash._basecreate": "~2.3.0", + "lodash._setbinddata": "~2.3.0", + "lodash.isobject": "~2.3.0" + } + }, + "node_modules/lodash._basecreate": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.3.0.tgz", + "integrity": "sha1-m4ioak3P97fzxh2Dovz8BnHsneA=", + "dependencies": { + "lodash._renative": "~2.3.0", + "lodash.isobject": "~2.3.0", + "lodash.noop": "~2.3.0" + } + }, + "node_modules/lodash._basecreatecallback": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.3.0.tgz", + "integrity": "sha1-N7KrF1kaM56YjbMln81GAZ16w2I=", + "dependencies": { + "lodash._setbinddata": "~2.3.0", + "lodash.bind": "~2.3.0", + "lodash.identity": "~2.3.0", + "lodash.support": "~2.3.0" + } + }, + "node_modules/lodash._basecreatewrapper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.3.0.tgz", + "integrity": "sha1-qgxhrZYETDkzN2ExSDqXWcNlEkc=", + "dependencies": { + "lodash._basecreate": "~2.3.0", + "lodash._setbinddata": "~2.3.0", + "lodash._slice": "~2.3.0", + "lodash.isobject": "~2.3.0" + } + }, + "node_modules/lodash._createwrapper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.3.0.tgz", + "integrity": "sha1-0arhEC2t9EDo4G/BM6bt1/4UYHU=", + "dependencies": { + "lodash._basebind": "~2.3.0", + "lodash._basecreatewrapper": "~2.3.0", + "lodash.isfunction": "~2.3.0" + } + }, + "node_modules/lodash._objecttypes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.3.0.tgz", + "integrity": "sha1-aj6jmH3W7rgCGy1cnDA1Scwrrh4=" + }, + "node_modules/lodash._renative": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._renative/-/lodash._renative-2.3.0.tgz", + "integrity": "sha1-d9jt1M7SbdWXH54Vpfdy5OMX+9M=" + }, + "node_modules/lodash._setbinddata": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.3.0.tgz", + "integrity": "sha1-5WEEkKzRMnfVmFjZW18nJ/FQjwQ=", + "dependencies": { + "lodash._renative": "~2.3.0", + "lodash.noop": "~2.3.0" + } + }, + "node_modules/lodash._shimkeys": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.3.0.tgz", + "integrity": "sha1-YR+TFJ4+bHIQlrSHae8pU3rai6k=", + "dependencies": { + "lodash._objecttypes": "~2.3.0" + } + }, + "node_modules/lodash._slice": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.3.0.tgz", + "integrity": "sha1-FHGYEyhZly5GgMoppZkshVZpqlw=" + }, + "node_modules/lodash.bind": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.3.0.tgz", + "integrity": "sha1-wqjhi2jl7MFS4rFoJmEW/qWwFsw=", + "dependencies": { + "lodash._createwrapper": "~2.3.0", + "lodash._renative": "~2.3.0", + "lodash._slice": "~2.3.0" + } + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.foreach": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.3.0.tgz", + "integrity": "sha1-CDQEyR6EbudyRf3512UZxosq8Wg=", + "dependencies": { + "lodash._basecreatecallback": "~2.3.0", + "lodash.forown": "~2.3.0" + } + }, + "node_modules/lodash.forown": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.3.0.tgz", + "integrity": "sha1-JPtKr4ANRfwtxgv+w84EyDajrX8=", + "dependencies": { + "lodash._basecreatecallback": "~2.3.0", + "lodash._objecttypes": "~2.3.0", + "lodash.keys": "~2.3.0" + } + }, + "node_modules/lodash.identity": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.3.0.tgz", + "integrity": "sha1-awGiEMlIU1XCqRO0i2cRIZoXPe0=" + }, + "node_modules/lodash.isfunction": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.3.0.tgz", + "integrity": "sha1-aylz5HpkfPEucNZ2rqE2Q3BuUmc=" + }, + "node_modules/lodash.isobject": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.3.0.tgz", + "integrity": "sha1-LhbT/Fg9qYMZaJU/LY5tc0NPZ5k=", + "dependencies": { + "lodash._objecttypes": "~2.3.0" + } + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.keys": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.3.0.tgz", + "integrity": "sha1-s1D0+Syqn0WkouzwGEVM8vKK4lM=", + "dependencies": { + "lodash._renative": "~2.3.0", + "lodash._shimkeys": "~2.3.0", + "lodash.isobject": "~2.3.0" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.noop": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.3.0.tgz", + "integrity": "sha1-MFnWKNUbv5N80qC2/Dp/ISpmnCw=" + }, + "node_modules/lodash.support": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.3.0.tgz", + "integrity": "sha1-fq8DivTw1qq3drRKptz8gDNMm/0=", + "dependencies": { + "lodash._renative": "~2.3.0" + } + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dependencies": { + "mime-db": "1.48.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-html-to-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/node-html-to-image/-/node-html-to-image-3.2.0.tgz", + "integrity": "sha512-DiG+jYiLYksolLiLVXcKQMYMuYCjXgizRASXFuz2wupO2zC8byTCDAPTDgDH0pQJ5QmONQ/l8v6evXsNQtKy3w==", + "dependencies": { + "handlebars": "^4.5.3", + "puppeteer": "3.0.4", + "puppeteer-cluster": "^0.21.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/prism-media": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz", + "integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==" + }, + "node_modules/probe-image-size": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.1.tgz", + "integrity": "sha512-d+6L3NvQBCNt4peRDoEfA7r9bPm6/qy18FnLKwg4NWBC5JrJm0pMLRg1kF4XNsPe1bUdt3WIMonPJzQWN2HXjQ==", + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.0.4.tgz", + "integrity": "sha512-1QEb4tJXXbNId7WSHlcDkS3B4GklTIebKn8Y9D6B7tAdUjQncb+8QlTjbQsAgGX5dhRG32Qycuk5XKzJgLs0sg==", + "dependencies": { + "debug": "^4.1.0", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer-cluster": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/puppeteer-cluster/-/puppeteer-cluster-0.21.0.tgz", + "integrity": "sha512-/x5mei0vXxFPpJ7iUS+xJ3rOcxxYUa2YeEyuWI9m0M5e8ammPiCXjvOsTcni+4ZAop3L2gpZFkxafPvXWOoRfg==", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=", + "dependencies": { + "debug": "2" + } + }, + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/system-sleep": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/system-sleep/-/system-sleep-1.3.7.tgz", + "integrity": "sha512-0Bd7sdWpO+UNLwPr7I4fhmaYf5RARbx4LQPgEPHVqU+uYc1C7FMfxyEWZlJXHpcGmtSR7KeR9npNpool+s8TvQ==", + "dependencies": { + "deasync-promise": "1.0.1" + } + }, + "node_modules/table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "node_modules/tmi.js": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/tmi.js/-/tmi.js-1.8.3.tgz", + "integrity": "sha512-1YAO3hwnkpAbUfVlpxcqOHP4Nun/+TqxWE/e/ZKMB6p40ZKczhI03oZW2Qx3pppqWjXz5kSjHdT/vTTfi2gCGQ==", + "dependencies": { + "node-fetch": "^2.6.1", + "ws": "^7.4.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.13.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.10.tgz", + "integrity": "sha512-57H3ACYFXeo1IaZ1w02sfA71wI60MGco/IQFjOqK+WtKoprh7Go2/yvd2HPtoJILO2Or84ncLccI4xoHMTSbGg==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz", + "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==", + "dependencies": { + "node-gyp-build": "^4.2.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/ws": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==", + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/wtf_wikipedia": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/wtf_wikipedia/-/wtf_wikipedia-8.5.1.tgz", + "integrity": "sha512-tvLDPsC9bGG9ABMijCCEFwPhK4678glSVX45UwXwT7KhTJgQFhn3cdm84cMOKSTag+/bRfgBG+qe9qXn3AyStw==", + "bin": { + "wtf_wikipedia": "cli.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + }, "dependencies": { "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "^7.10.4" } }, "@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" - } - }, - "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } } }, "@discordjs/collection": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.5.tgz", - "integrity": "sha512-CU1q0UXQUpFNzNB7gufgoisDHP7n+T3tkqTsp3MNUkVJ5+hS3BCvME8uCXAUFlz+6T2FbTCu75A+yQ7HMKqRKw==" + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", + "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" }, "@discordjs/form-data": { "version": "3.0.1", @@ -54,17 +2451,37 @@ "mime-types": "^2.1.12" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "@types/node": { + "version": "15.12.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.5.tgz", + "integrity": "sha512-se3yX7UHv5Bscf8f1ERKvQOD6sTyycH3hdaoozvaLxgUiY5lIGEeH37AD0G0Qi9kPqihPn0HOfd2yaIEN9VwEg==", + "optional": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "optional": true, + "requires": { + "@types/node": "*" + } }, "abort-controller": { "version": "3.0.0", @@ -75,21 +2492,26 @@ } }, "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" + }, "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -98,27 +2520,16 @@ "uri-js": "^4.2.2" } }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -130,6 +2541,53 @@ "color-convert": "^1.9.0" } }, + "archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "requires": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + } + }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -140,32 +2598,80 @@ } }, "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "bufferutil": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", + "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", + "requires": { + "node-gyp-build": "^4.2.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -173,42 +2679,60 @@ "dev": true }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "restore-cursor": "^3.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "color-convert": { "version": "1.9.3", @@ -238,50 +2762,63 @@ "delayed-stream": "~1.0.0" } }, - "compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true + "compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, + "convert-units": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/convert-units/-/convert-units-2.3.4.tgz", + "integrity": "sha512-ERHfdA0UhHJp1IpwE6PnFJx8LqG7B1ZjJ20UvVCmopEnVCfER68Tbe3kvN63dLbYXDA2xFWRE6zd4Wsf0w7POg==", + "requires": { + "lodash.foreach": "2.3.x", + "lodash.keys": "2.3.x" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, + "crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "d": { @@ -293,13 +2830,29 @@ "type": "^1.0.1" } }, + "deasync": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.21.tgz", + "integrity": "sha512-kUmM8Y+PZpMpQ+B4AuOW9k2Pfx/mSupJtxOsLzmnHY2WqZUYRFccFn2RhzPAqt3Xb+sorK/badW2D4zNzqZz5w==", + "requires": { + "bindings": "^1.5.0", + "node-addon-api": "^1.7.1" + } + }, + "deasync-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deasync-promise/-/deasync-promise-1.0.1.tgz", + "integrity": "sha1-KyfeR4Fnr07zS6mYecUuwM7dYcI=", + "requires": { + "deasync": "^0.1.7" + } + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "deep-is": { @@ -313,19 +2866,24 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "diacritics": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", + "integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E=" + }, "discord.js": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz", - "integrity": "sha512-Ueb/0SOsxXyqwvwFYFe0msMrGqH1OMqpp2Dpbplnlr4MzcRrFWwsBM9gKNZXPVBHWUKiQkwU8AihXBXIvTTSvg==", + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", + "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", "requires": { - "@discordjs/collection": "^0.1.5", + "@discordjs/collection": "^0.1.6", "@discordjs/form-data": "^3.0.1", "abort-controller": "^3.0.0", - "node-fetch": "^2.6.0", - "prism-media": "^1.2.0", + "node-fetch": "^2.6.1", + "prism-media": "^1.2.9", "setimmediate": "^1.0.5", "tweetnacl": "^1.0.3", - "ws": "^7.2.1" + "ws": "^7.4.4" } }, "doctrine": { @@ -343,13 +2901,21 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "ansi-colors": "^4.1.1" } }, "es5-ext": { @@ -382,90 +2948,108 @@ } }, "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" } }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "esprima": { @@ -475,29 +3059,37 @@ "dev": true }, "esquery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", - "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { - "estraverse": "^5.0.0" + "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", - "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -517,6 +3109,11 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" + }, "ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -526,27 +3123,27 @@ }, "dependencies": { "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" } } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" } }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { @@ -561,65 +3158,53 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "requires": { - "escape-string-regexp": "^1.0.5" + "pend": "~1.2.0" } }, "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "flat-cache": "^3.0.4" } }, - "find-versions": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", - "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", - "dev": true, - "requires": { - "semver-regex": "^2.0.0" - } + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" } }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "functional-red-black-tree": { "version": "1.0.1", @@ -627,11 +3212,18 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -642,21 +3234,38 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" } }, "has-flag": { @@ -670,85 +3279,39 @@ "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.2.tgz", "integrity": "sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA==" }, - "husky": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", - "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==", - "dev": true, + "homoglyph-search": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/homoglyph-search/-/homoglyph-search-1.2.1.tgz", + "integrity": "sha512-ykghjGrEUo3oMw88fiNBfbsip18XYg2IgQyN/esf9Fy0qNRY9FHBpWKzMNT0pDJ8img4WHdUqszQifFSb/JtrA==" + }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", "requires": { - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.6.0", - "cosmiconfig": "^6.0.0", - "find-versions": "^3.2.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^4.2.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "agent-base": "5", + "debug": "4" } }, + "husky": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", + "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -756,9 +3319,9 @@ "dev": true }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -775,7 +3338,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -784,96 +3346,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-extglob": { "version": "2.1.1", @@ -896,17 +3369,16 @@ "is-extglob": "^2.1.1" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -920,21 +3392,15 @@ "dev": true }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" } }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -947,96 +3413,285 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash._basebind": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.3.0.tgz", + "integrity": "sha1-K1vEUqDhBhQ7IYafIzvbWHQX0kg=", + "requires": { + "lodash._basecreate": "~2.3.0", + "lodash._setbinddata": "~2.3.0", + "lodash.isobject": "~2.3.0" + } + }, + "lodash._basecreate": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.3.0.tgz", + "integrity": "sha1-m4ioak3P97fzxh2Dovz8BnHsneA=", + "requires": { + "lodash._renative": "~2.3.0", + "lodash.isobject": "~2.3.0", + "lodash.noop": "~2.3.0" + } + }, + "lodash._basecreatecallback": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.3.0.tgz", + "integrity": "sha1-N7KrF1kaM56YjbMln81GAZ16w2I=", + "requires": { + "lodash._setbinddata": "~2.3.0", + "lodash.bind": "~2.3.0", + "lodash.identity": "~2.3.0", + "lodash.support": "~2.3.0" + } + }, + "lodash._basecreatewrapper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.3.0.tgz", + "integrity": "sha1-qgxhrZYETDkzN2ExSDqXWcNlEkc=", + "requires": { + "lodash._basecreate": "~2.3.0", + "lodash._setbinddata": "~2.3.0", + "lodash._slice": "~2.3.0", + "lodash.isobject": "~2.3.0" + } + }, + "lodash._createwrapper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.3.0.tgz", + "integrity": "sha1-0arhEC2t9EDo4G/BM6bt1/4UYHU=", + "requires": { + "lodash._basebind": "~2.3.0", + "lodash._basecreatewrapper": "~2.3.0", + "lodash.isfunction": "~2.3.0" + } + }, + "lodash._objecttypes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.3.0.tgz", + "integrity": "sha1-aj6jmH3W7rgCGy1cnDA1Scwrrh4=" + }, + "lodash._renative": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._renative/-/lodash._renative-2.3.0.tgz", + "integrity": "sha1-d9jt1M7SbdWXH54Vpfdy5OMX+9M=" + }, + "lodash._setbinddata": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.3.0.tgz", + "integrity": "sha1-5WEEkKzRMnfVmFjZW18nJ/FQjwQ=", + "requires": { + "lodash._renative": "~2.3.0", + "lodash.noop": "~2.3.0" + } + }, + "lodash._shimkeys": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.3.0.tgz", + "integrity": "sha1-YR+TFJ4+bHIQlrSHae8pU3rai6k=", + "requires": { + "lodash._objecttypes": "~2.3.0" } }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "lodash._slice": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.3.0.tgz", + "integrity": "sha1-FHGYEyhZly5GgMoppZkshVZpqlw=" + }, + "lodash.bind": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.3.0.tgz", + "integrity": "sha1-wqjhi2jl7MFS4rFoJmEW/qWwFsw=", + "requires": { + "lodash._createwrapper": "~2.3.0", + "lodash._renative": "~2.3.0", + "lodash._slice": "~2.3.0" + } + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.foreach": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.3.0.tgz", + "integrity": "sha1-CDQEyR6EbudyRf3512UZxosq8Wg=", + "requires": { + "lodash._basecreatecallback": "~2.3.0", + "lodash.forown": "~2.3.0" + } + }, + "lodash.forown": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.3.0.tgz", + "integrity": "sha1-JPtKr4ANRfwtxgv+w84EyDajrX8=", + "requires": { + "lodash._basecreatecallback": "~2.3.0", + "lodash._objecttypes": "~2.3.0", + "lodash.keys": "~2.3.0" + } + }, + "lodash.identity": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.3.0.tgz", + "integrity": "sha1-awGiEMlIU1XCqRO0i2cRIZoXPe0=" + }, + "lodash.isfunction": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.3.0.tgz", + "integrity": "sha1-aylz5HpkfPEucNZ2rqE2Q3BuUmc=" + }, + "lodash.isobject": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.3.0.tgz", + "integrity": "sha1-LhbT/Fg9qYMZaJU/LY5tc0NPZ5k=", + "requires": { + "lodash._objecttypes": "~2.3.0" + } + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.keys": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.3.0.tgz", + "integrity": "sha1-s1D0+Syqn0WkouzwGEVM8vKK4lM=", + "requires": { + "lodash._renative": "~2.3.0", + "lodash._shimkeys": "~2.3.0", + "lodash.isobject": "~2.3.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.noop": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.3.0.tgz", + "integrity": "sha1-MFnWKNUbv5N80qC2/Dp/ISpmnCw=" + }, + "lodash.support": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.3.0.tgz", + "integrity": "sha1-fq8DivTw1qq3drRKptz8gDNMm/0=", "requires": { - "p-locate": "^4.1.0" + "lodash._renative": "~2.3.0" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" + }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "requires": { - "mime-db": "1.43.0" + "mime-db": "1.48.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, - "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "natural-compare": { "version": "1.4.0", @@ -1044,90 +3699,91 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { - "mimic-fn": "^2.1.0" + "whatwg-url": "^5.0.0" } }, - "opencollective-postinstall": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", - "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", - "dev": true + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, + "node-html-to-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/node-html-to-image/-/node-html-to-image-3.2.0.tgz", + "integrity": "sha512-DiG+jYiLYksolLiLVXcKQMYMuYCjXgizRASXFuz2wupO2zC8byTCDAPTDgDH0pQJ5QmONQ/l8v6evXsNQtKy3w==", "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "handlebars": "^4.5.3", + "puppeteer": "3.0.4", + "puppeteer-cluster": "^0.21.0" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "p-try": "^2.0.0" + "wrappy": "1" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -1137,76 +3793,71 @@ "callsites": "^3.0.0" } }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, + "prism-media": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz", + "integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==" + }, + "probe-image-size": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.1.tgz", + "integrity": "sha512-d+6L3NvQBCNt4peRDoEfA7r9bPm6/qy18FnLKwg4NWBC5JrJm0pMLRg1kF4XNsPe1bUdt3WIMonPJzQWN2HXjQ==", "requires": { - "semver-compare": "^1.0.0" + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" } }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prism-media": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.1.tgz", - "integrity": "sha512-R3EbKwJiYlTvGwcG1DpUt+06DsxOGS5W4AMEHT7oVOjG93MjpdhGX1whHyjnqknylLMupKAsKMEXcTNRbPe6Vw==" + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, "punycode": { "version": "2.1.1", @@ -1214,16 +3865,59 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true + "puppeteer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.0.4.tgz", + "integrity": "sha512-1QEb4tJXXbNId7WSHlcDkS3B4GklTIebKn8Y9D6B7tAdUjQncb+8QlTjbQsAgGX5dhRG32Qycuk5XKzJgLs0sg==", + "requires": { + "debug": "^4.1.0", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + } + }, + "puppeteer-cluster": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/puppeteer-cluster/-/puppeteer-cluster-0.21.0.tgz", + "integrity": "sha512-/x5mei0vXxFPpJ7iUS+xJ3rOcxxYUa2YeEyuWI9m0M5e8ammPiCXjvOsTcni+4ZAop3L2gpZFkxafPvXWOoRfg==", + "requires": { + "debug": "^4.1.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "requires": { + "minimatch": "^3.0.4" + } }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "resolve-from": { @@ -1232,66 +3926,37 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" } }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, - "semver-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", - "dev": true + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "setimmediate": { "version": "1.0.5", @@ -1299,100 +3964,123 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, + "stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=", "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "debug": "2" }, "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ansi-regex": "^5.0.0" + "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } + "ansi-regex": "^5.0.0" } }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { @@ -1404,43 +4092,71 @@ "has-flag": "^3.0.0" } }, + "system-sleep": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/system-sleep/-/system-sleep-1.3.7.tgz", + "integrity": "sha512-0Bd7sdWpO+UNLwPr7I4fhmaYf5RARbx4LQPgEPHVqU+uYc1C7FMfxyEWZlJXHpcGmtSR7KeR9npNpool+s8TvQ==", + "requires": { + "deasync-promise": "1.0.1" + } + }, "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "ajv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true } } }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -1450,23 +4166,21 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, + "tmi.js": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/tmi.js/-/tmi.js-1.8.3.tgz", + "integrity": "sha512-1YAO3hwnkpAbUfVlpxcqOHP4Nun/+TqxWE/e/ZKMB6p40ZKczhI03oZW2Qx3pppqWjXz5kSjHdT/vTTfi2gCGQ==", "requires": { - "os-tmpdir": "~1.0.2" + "node-fetch": "^2.6.1", + "ws": "^7.4.3" } }, - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", - "dev": true + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "tweetnacl": { "version": "1.0.3", @@ -1479,18 +4193,18 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, "typedarray-to-buffer": { @@ -1501,30 +4215,64 @@ "is-typedarray": "^1.0.0" } }, + "uglify-js": { + "version": "3.13.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.10.tgz", + "integrity": "sha512-57H3ACYFXeo1IaZ1w02sfA71wI60MGco/IQFjOqK+WtKoprh7Go2/yvd2HPtoJILO2Or84ncLccI4xoHMTSbGg==", + "optional": true + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" } }, + "utf-8-validate": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz", + "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==", + "requires": { + "node-gyp-build": "^4.2.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, "websocket": { - "version": "1.0.31", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.31.tgz", - "integrity": "sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ==", + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", "requires": { + "bufferutil": "^4.0.1", "debug": "^2.2.0", "es5-ext": "^0.10.50", - "nan": "^2.14.0", "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", "yaeti": "^0.0.6" }, "dependencies": { @@ -1543,64 +4291,78 @@ } } }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "dev": true - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==" }, "wtf_wikipedia": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/wtf_wikipedia/-/wtf_wikipedia-8.1.2.tgz", - "integrity": "sha512-iCLwYtwazY0l+6nv5h0swETSIjtigEQJEVoyeE58lNGd5gQhtyrKV7C4juW4ijYHKmYYMiJKq8uUbnaj1JrvUA==" + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/wtf_wikipedia/-/wtf_wikipedia-8.5.1.tgz", + "integrity": "sha512-tvLDPsC9bGG9ABMijCCEFwPhK4678glSVX45UwXwT7KhTJgQFhn3cdm84cMOKSTag+/bRfgBG+qe9qXn3AyStw==" }, "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, - "yaml": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.1.tgz", - "integrity": "sha512-xbWX1ayUVoW8DPM8qxOBowac4XxSTi0mFLbiokRq880ViYglN+F3nJ4Dc2GdypXpykrknKS39d8I3lzFoHv1kA==", - "dev": true, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "requires": { - "@babel/runtime": "^7.9.2" + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" } } } diff --git a/package.json b/package.json index a9f0117..58a1104 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,32 @@ { "name": "moode-rewrite", - "version": "2.0.0", + "version": "2.6.0", "description": "re-write of discord chatbot", - "main": "app.js", + "main": "moode.js", "scripts": { + "start": "node moode.js", "test": "eslint . --cache" }, "author": "LegoFigure11", "license": "MIT", "dependencies": { + "archiver": "^5.3.0", "colors": "^1.4.0", - "discord.js": "^12.2.0", + "convert-units": "^2.3.4", + "diacritics": "^1.3.0", + "discord.js": "^12.5.3", "hex2dec": "^1.1.2", - "websocket": "^1.0.31", - "wtf_wikipedia": "^8.1.2" + "homoglyph-search": "^1.2.1", + "node-html-to-image": "^3.2.0", + "probe-image-size": "^7.2.1", + "system-sleep": "^1.3.7", + "tmi.js": "^1.8.3", + "websocket": "^1.0.34", + "wtf_wikipedia": "^8.5.1" }, "devDependencies": { - "eslint": "^6.8.0", - "husky": "^4.2.5" + "eslint": "^7.29.0", + "husky": "^6.0.0" }, "husky": { "hooks": { diff --git a/showdown/app.js b/showdown/app.js new file mode 100644 index 0000000..1b0234a --- /dev/null +++ b/showdown/app.js @@ -0,0 +1,18 @@ +"use strict"; + +global.psConfig = require("./config.json"); +global.psRooms = require("./src/rooms.js").Rooms; +global.psUsers = require("./src/users.js").Users; +global.psMessageParser = require("./src/messageParser.js").MessageParser; +global.Tournaments = require("./src/tournaments.js"); + +const ShowdownClient = require("./src/client.js"); +global.psClient = new ShowdownClient(); + +(async () => { + global.PsCommandHandler = require("./src/commandHandler.js"); + global.psCommandHandler = new PsCommandHandler(); + await psCommandHandler.init(); + await psMessageParser.init(); + psClient.connect(); +})(); diff --git a/showdown/commands/bird.js b/showdown/commands/bird.js new file mode 100644 index 0000000..80066b2 --- /dev/null +++ b/showdown/commands/bird.js @@ -0,0 +1,55 @@ +"use strict"; + +const https = require("https"); +const probe = require("probe-image-size"); + +module.exports = { + desc: "Gets a random bird photo.", + aliases: ["randombird", "randbird", "birb", "randombirb", "randbirb"], + roomRank: "+", + async process(args, room, user) { + const options = { + hostname: "shibe.online", + path: "/api/birds?count=1&urls=true&httpsUrls=true", + method: "GET", + }; + const bird = new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + res.on("data", (res) => { + resolve(res); + }); + }); + + req.on("error", (err) => { + resolve(err); + }); + req.end(); + }); + const data = await bird; + + const dimensions = await fitImage(JSON.parse(data.toString())[0], 300, 400); + + const id = room.id === "dreamyard" ? Tools.toId(JSON.parse(data.toString())[0]) : "randbird"; + + return room.say(`/adduhtml ${id}, `); + }, +}; + +// From Asheviere/Kid-A +// https://git.io/JfOyK + +async function fitImage(url, maxHeight, maxWidth) { + const {height, width} = await probe(url); + + let ratio = 1; + + if (width <= maxWidth && height <= maxHeight) return [width, height]; + + if (height * (maxWidth / maxHeight) > width) { + ratio = maxHeight / height; + } else { + ratio = maxWidth / width; + } + + return [Math.round(width * ratio), Math.round(height * ratio)]; +} diff --git a/showdown/commands/cat.js b/showdown/commands/cat.js new file mode 100644 index 0000000..ee0c2ee --- /dev/null +++ b/showdown/commands/cat.js @@ -0,0 +1,55 @@ +"use strict"; + +const https = require("https"); +const probe = require("probe-image-size"); + +module.exports = { + desc: "Gets a random cat photo.", + aliases: ["randcat", "randomcat"], + roomRank: "+", + async process(args, room, user) { + const options = { + hostname: "aws.random.cat", + path: "/meow?filter=mp4,webm", + method: "GET", + }; + const cat = new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + res.on("data", (res) => { + resolve(res); + }); + }); + + req.on("error", (err) => { + resolve(err); + }); + req.end(); + }); + const data = await cat; + + const dimensions = await fitImage(JSON.parse(data.toString()).file, 300, 400); + + const id = room.id === "dreamyard" ? Tools.toId(JSON.parse(data.toString()).file) : "randcat"; + + return room.say(`/adduhtml ${id}, `); + }, +}; + +// From Asheviere/Kid-A +// https://git.io/JfOyK + +async function fitImage(url, maxHeight, maxWidth) { + const {height, width} = await probe(url); + + let ratio = 1; + + if (width <= maxWidth && height <= maxHeight) return [width, height]; + + if (height * (maxWidth / maxHeight) > width) { + ratio = maxHeight / height; + } else { + ratio = maxWidth / width; + } + + return [Math.round(width * ratio), Math.round(height * ratio)]; +} diff --git a/showdown/commands/chinese/f.js b/showdown/commands/chinese/f.js new file mode 100644 index 0000000..964660f --- /dev/null +++ b/showdown/commands/chinese/f.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + desc: "Pays respects.", + roomRank: "+", + async process(args, room, user) { + return room.say(`${user.name}表示敬意`); + }, +}; diff --git a/showdown/commands/chinese/ohko.js b/showdown/commands/chinese/ohko.js new file mode 100644 index 0000000..370ee75 --- /dev/null +++ b/showdown/commands/chinese/ohko.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + desc: "Uses a One-hit KO move.", + roomRank: "+", + async process(args, room, user) { + return room.say(Tools.random(10) < 3 ? "一击必杀!" /* It's a one-hit KO! */ : "没能命中!" /* The attack missed! */); + }, +}; diff --git a/showdown/commands/chinese/weebify.js b/showdown/commands/chinese/weebify.js new file mode 100644 index 0000000..b1918a3 --- /dev/null +++ b/showdown/commands/chinese/weebify.js @@ -0,0 +1,29 @@ +"use strict"; + +const phrases = [ + "桑", // san + "君", // kun + "酱", // chan + "大人", // sama + "先生", // sensei + "前辈", // senpai + "聚聚", // pro gamer + "同志", // comrade + "老师", // teacher + "将军", // army general + "小姐", // lass + "大哥", // big brother + "少爷", // sama (alt) + "老弟", // big younger brother + "女士", // lass (alt) +]; + +module.exports = { + desc: " (* ^ ω ^)/*:・゚✧*:・゚✧", + usage: "", + roomRank: "+", + async process(args, room, user) { + if (!args[0]) return; + return room.say(`${args.join(", ")}${Tools.sampleOne(phrases)}`); + }, +}; diff --git a/showdown/commands/dec2hex.js b/showdown/commands/dec2hex.js new file mode 100644 index 0000000..80e0ebb --- /dev/null +++ b/showdown/commands/dec2hex.js @@ -0,0 +1,13 @@ +"use strict"; + +const converter = require("hex2dec"); + +module.exports = { + desc: "Converts a provided decimal value to hexadecimal.", + usage: "", + aliases: ["hexify", "d2h", "dh", "dectohex", "decimaltohex", "decimaltohexadecimal"], + async process(args, room, user) { + if (!(args[0])) return room.say(`Usage: \`${psConfig.commandCharacter}dec2hex ${this.usage}\``); + return room.say(converter.decToHex(args[0])); + }, +}; diff --git a/showdown/commands/dev/alts.js b/showdown/commands/dev/alts.js new file mode 100644 index 0000000..bfb9181 --- /dev/null +++ b/showdown/commands/dev/alts.js @@ -0,0 +1,27 @@ +"use strict"; + +module.exports = { + desc: "Gets all known alts of a user.", + usage: "", + developerOnly: true, + async process(args, room, user) { + if (!args[0]) return user.say(`Usage: \`\`${psConfig.commandCharacter}alts ${this.usage}\`\``); + const db = Storage.getDatabase("alts"); + const target = Tools.toId(args[0]); + const alts = []; + if (db[target]) { + for (const name of db[target]) { + alts.push(name); + } + } + const keys = Object.keys(db); + for (let i = 0; i < keys.length; i++) { + if (db[keys[i]].includes(target)) { + for (const name of db[keys[i]]) { + alts.push(name); + } + } + } + return room.say(alts.length > 1 ? `**${args[0]}**'s known alts: ${[...new Set(alts)].sort().join(", ")}` : `No alts found for user: ${target}`); + }, +}; diff --git a/showdown/commands/dev/eval.js b/showdown/commands/dev/eval.js new file mode 100644 index 0000000..0112472 --- /dev/null +++ b/showdown/commands/dev/eval.js @@ -0,0 +1,23 @@ +"use strict"; + +const LCRNG = require("../../../sources/rng/lcrng.js"); // eslint-disable-line +const utilities = require("../../src/utilities.js"); // eslint-disable-line + +module.exports = { + desc: "Evaluates arbitrary javascript.", + usage: "", + aliases: ["js", "evaluate"], + developerOnly: true, + async process(args, room, user) { + args = args.join(",").trim(); + let output; + try { + output = eval(args); + output = JSON.stringify(output, null, 2); + console.log(output); + } catch (e) { + return room.say(`Error while evaluating expression: ${e}`); + } + return room.say(output); + }, +}; diff --git a/showdown/commands/dev/hotpatch.js b/showdown/commands/dev/hotpatch.js new file mode 100644 index 0000000..ba6bbcd --- /dev/null +++ b/showdown/commands/dev/hotpatch.js @@ -0,0 +1,27 @@ +"use strict"; + +module.exports = { + desc: "Live reloads modules.", + aliases: ["reload", "rl"], + developerOnly: true, + async process(args, room, user) { + console.log(`${Tools.showdownText()}Hot-patching...`); + console.log(`${Tools.showdownText()}--------------`); + Tools.uncacheDir("showdown/"); + Tools.uncacheDir("sources/"); + + global.Storage = require("../../../sources/storage.js"); + Storage.importDatabases(); + global.Tools = require("../../../sources/tools.js"); + + global.psMessageParser = require("../../src/messageParser.js").MessageParser; + + global.PsCommandHandler = require("../../src/commandHandler.js"); + global.psCommandHandler = new PsCommandHandler(); + psMessageParser.init(true); + psCommandHandler.init(true); + + room.say("Hotpatch completed!"); + return true; + }, +}; diff --git a/showdown/commands/dev/ip.js b/showdown/commands/dev/ip.js new file mode 100644 index 0000000..dea425c --- /dev/null +++ b/showdown/commands/dev/ip.js @@ -0,0 +1,15 @@ +"use strict"; + +const child_process = require("child_process"); +const util = require("util"); + +const exec = util.promisify(child_process.exec); + +module.exports = { + desc: "Returns the bot's public IP address.", + developerOnly: true, + async process(args, room, user) { + const ip = await exec("curl ifconfig.me").catch(e => console.log(e)); + return user.say(ip.stdout); + }, +}; diff --git a/showdown/commands/dev/kill.js b/showdown/commands/dev/kill.js new file mode 100644 index 0000000..8f751fc --- /dev/null +++ b/showdown/commands/dev/kill.js @@ -0,0 +1,11 @@ +"use strict"; + +module.exports = { + desc: "Ends the current bot process.", + developerOnly: true, + async process(args, room, user) { + console.log(`${Tools.showdownText()}${"Ending all bot processes...".brightRed}`); + console.log(`${Tools.showdownText()}${"This can now be safely closed!".green}`); + eval("process.exit(0);"); + }, +}; diff --git a/showdown/commands/dev/pm.js b/showdown/commands/dev/pm.js new file mode 100644 index 0000000..b8ed07b --- /dev/null +++ b/showdown/commands/dev/pm.js @@ -0,0 +1,19 @@ +"use strict"; + +module.exports = { + desc: "Makes the bot send a user a message.", + usage: ", ", + developerOnly: true, + async process(args, room, user) { + if (!args[1]) return user.say(`${psConfig.commandCharacter}pm ${this.usage}`); + const message = []; + for (let i = 1; i < args.length; i++) { + message.push(args[i]); + } + const target = psUsers.get(args.shift()); + if (!target) return user.say(`User "${args[0]}" not found!`); + args[0] = args[0].trim(); // Clear leading whitespace to allow for entry of commands + + return room.say(`/pm ${target.id}, ${args.join(",")}`); + }, +}; diff --git a/showdown/commands/dev/say.js b/showdown/commands/dev/say.js new file mode 100644 index 0000000..1663024 --- /dev/null +++ b/showdown/commands/dev/say.js @@ -0,0 +1,16 @@ +"use strict"; + +module.exports = { + desc: "Makes the bot send a message.", + usage: "<(optional) room>, ", + developerOnly: true, + async process(args, room, user) { + if (!args[1]) return room.say(args[0]); + const message = []; + for (let i = 1; i < args.length; i++) { + message.push(args[i]); + } + message[0] = message[0].trim(); // Clear leading whitespace to allow for entry of commands + return psClient.send(`${args[0]}|${message.join(",")}`); + }, +}; diff --git a/showdown/commands/dev/uptime.js b/showdown/commands/dev/uptime.js new file mode 100644 index 0000000..32af19c --- /dev/null +++ b/showdown/commands/dev/uptime.js @@ -0,0 +1,42 @@ +// Adapted from Scrappie +// https://github.com/Hidden50/Pokemon-Showdown-Node-Bot/blob/master/commands/base-basic.js#L36 +"use strict"; + +module.exports = { + desc: "Prints information about how long the bot has been running.", + async process(args, room, user) { + let text = ""; + if (!room.isPm() && !user.hasRoomRank(room, "@") && !user.isDeveloper()) text = `/pm ${user.id}, `; + text += "**Uptime:** "; + const divisors = [52, 7, 24, 60, 60]; + const units = ["week", "day", "hour", "minute", "second"]; + const buffer = []; + let uptime = ~~(process.uptime()); + do { + const divisor = divisors.pop(); + const unit = uptime % divisor; + buffer.push(unit > 1 ? `${unit} ${units.pop()}s` : `${unit} ${units.pop()}`); + uptime = ~~(uptime / divisor); + } while (uptime); + + switch (buffer.length) { + case 5: + text += `${buffer[4]}, `; + /* falls through */ + case 4: + text += `${buffer[3]}, `; + /* falls through */ + case 3: + text += `${buffer[2]}, ${buffer[1]}, and ${buffer[0]}`; + break; + case 2: + text += `${buffer[1]} and ${buffer[0]}`; + break; + case 1: + text += buffer[0]; + break; + } + + room.say(text); + }, +}; diff --git a/showdown/commands/dex/sprite.js b/showdown/commands/dex/sprite.js new file mode 100644 index 0000000..f7f338f --- /dev/null +++ b/showdown/commands/dex/sprite.js @@ -0,0 +1,96 @@ +"use strict"; + +const https = require("https"); + +const SPRITE_URL = "https://play.pokemonshowdown.com/sprites/"; + +module.exports = { + desc: "Image link of a Pokémon, or link to sprite directory if no argument is given. Uses PokemonShowdown's sprite library.", + usage: "", + aliases: ["gif", "model"], + options: ["shiny", "back", "female", "noani", "afd", "gen"], + hasCustomFormatting: true, + async process(args, room, user, dex) { + if (!(args[0])) { + return user.say("All sprites can be viewed here: https://play.pokemonshowdown.com/sprites/"); + } + + let pokemon = dex.getSpecies(args[0]); + if (!pokemon || !pokemon.exists) { + pokemon = dex.dataSearch(args[0], ["Pokedex"]); + if (!pokemon) { + return room.say(`No Pokémon ${args[0]} found.`); + } + pokemon = dex.getSpecies(pokemon[0].name); + } + + if (pokemon.gen > dex.gen) { + return room.say(`${pokemon.name} did not exist in Gen ${dex.gen}; it was introduced in Gen ${pokemon.gen}.`); + } + + let spriteId = pokemon.spriteid; + let genNum = dex.gen; + if (pokemon.tier === "CAP") { + genNum = 5; + } + if (pokemon.num === 0) { + genNum = 1; + } + + let genData = {1: "gen1", 2: "gen2", 3: "gen3", 4: "gen4", 5: "gen5", 6: "", 7: "", 8: ""}[genNum]; + let ending = ".png"; + if (!(Tools.toId(args).includes("noani")) && genNum >= 5 && pokemon.num > 0) { + genData += "ani"; + ending = ".gif"; + } + if (Tools.toId(args).includes("noani") && genNum >= 7 && !(Tools.toId(args).includes("back"))) { + genData = "dex"; + } + + let dir = ""; + + if (Tools.toId(args).includes("back")) { + dir += "-back"; + } + if (Tools.toId(args).includes("shiny") && genNum > 1) { + dir += "-shiny"; + } + if (Tools.toId(args).includes("afd")) { + dir = `afd${dir}`; + return `${SPRITE_URL}${dir}/${spriteId}.png`; + } + + dir = genData + dir; + if (dir === "") { + dir = "gen6"; + } + + if (Tools.toId(args).includes("female") && genNum >= 4) { + const checker = new Promise((resolve, reject) => { + const req = https.request({"host": "play.pokemonshowdown.com", + "method": "HEAD", + "path": `${SPRITE_URL}${dir}/${spriteId}-f${ending}`, + }); + + req.on("response", res => { + resolve(res); + }); + + req.on("error", err => { + reject(err); + }); + + req.end(); + }); + + const result = await checker; + if (result.statusCode === 200) { + spriteId += "-f"; + } + } + + const html = ``; + + return room.say(`/adduhtml ${pokemon.name}-sprite-${genNum}, ${html}`); + }, +}; diff --git a/showdown/commands/dog.js b/showdown/commands/dog.js new file mode 100644 index 0000000..aa06c03 --- /dev/null +++ b/showdown/commands/dog.js @@ -0,0 +1,55 @@ +"use strict"; + +const https = require("https"); +const probe = require("probe-image-size"); + +module.exports = { + desc: "Gets a random dog photo.", + aliases: ["randomdog", "randdog"], + roomRank: "+", + async process(args, room, user) { + const options = { + hostname: "random.dog", + path: "/woof?filter=mp4,webm", + method: "GET", + }; + const dog = new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + res.on("data", (res) => { + resolve(res); + }); + }); + + req.on("error", (err) => { + resolve(err); + }); + req.end(); + }); + const data = await dog; + + const dimensions = await fitImage(`https://random.dog/${data.toString()}`, 300, 400); + + const id = room.id === "dreamyard" ? Tools.toId(data.toString()) : "randdog"; + + return room.say(`/adduhtml ${id}, `); + }, +}; + +// From Asheviere/Kid-A +// https://git.io/JfOyK + +async function fitImage(url, maxHeight, maxWidth) { + const {height, width} = await probe(url); + + let ratio = 1; + + if (width <= maxWidth && height <= maxHeight) return [width, height]; + + if (height * (maxWidth / maxHeight) > width) { + ratio = maxHeight / height; + } else { + ratio = maxWidth / width; + } + + return [Math.round(width * ratio), Math.round(height * ratio)]; +} diff --git a/showdown/commands/f.js b/showdown/commands/f.js new file mode 100644 index 0000000..e1675cb --- /dev/null +++ b/showdown/commands/f.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + desc: "Pays respects.", + roomRank: "+", + async process(args, room, user) { + return room.say(`${user.name} paid their respects.`); + }, +}; diff --git a/showdown/commands/hex2dec.js b/showdown/commands/hex2dec.js new file mode 100644 index 0000000..0eba661 --- /dev/null +++ b/showdown/commands/hex2dec.js @@ -0,0 +1,13 @@ +"use strict"; + +const converter = require("hex2dec"); + +module.exports = { + desc: "Converts a provided hex value to decimal.", + usage: "", + aliases: ["decimalify", "h2d", "hd", "hextodec", "hextodecimal", "hexadecimaltodecimal"], + async process(args, room, user) { + if (!(args[0])) return room.say(`Usage: \`${psConfig.commandCharacter}hex2dec ${this.usage}\``); + return room.say(converter.hexToDec(args[0])); + }, +}; diff --git a/showdown/commands/mail.js b/showdown/commands/mail.js new file mode 100644 index 0000000..540013c --- /dev/null +++ b/showdown/commands/mail.js @@ -0,0 +1,41 @@ +// Mail delivery is handled in showdown/src/messageParser.js +// This command exists purely to queue mail + +"use strict"; + +const utilities = require("../src/utilities.js"); + +module.exports = { + desc: "Sends a user mail.", + usage: ", ", + pmOnly: true, + async process(args, room, user) { + if (!psConfig.allowMail) return room.say("Mail is not configured on this bot!"); + if (!args[1]) return room.say(`${psConfig.commandCharacter}mail ${this.usage}`); + + const to = Tools.toId(args[0]); + if (!to || to.length > 18 || to === psUsers.self.id || to.startsWith("guest")) return room.say("Please enter a valid username."); + + const message = args.slice(1).join(",").trim(); + const id = Tools.toId(message); + if (!id) return room.say("Please include a message to send."); + if (message.length > (258 - user.name.length)) return room.say("Your message is too long."); + + await utilities.checkForDb("mail", "{}"); + const db = Storage.getDatabase("mail"); + + if (to in db) { + let queued = 0; + for (let i = 0, len = db[to].length; i < len; i++) { + if (Tools.toId(db[to][i].from) === user.id) queued++; + } + if (queued >= 3 && !user.isDeveloper()) return room.say(`You have too many messages queued for ${psUsers.add(args[0]).name}.`); + } else { + db[to] = []; + } + db[to].push({time: Date.now(), from: user.name, text: message}); + Storage.exportDatabase("mail"); + + room.say(`Your message has been sent to ${psUsers.add(args[0]).name}!`); + }, +}; diff --git a/showdown/commands/namecolor.js b/showdown/commands/namecolor.js new file mode 100644 index 0000000..a475543 --- /dev/null +++ b/showdown/commands/namecolor.js @@ -0,0 +1,11 @@ +"use strict"; + +module.exports = { + desc: "Gets the color of a user's name.", + roomRank: "+", + aliases: ["namecolour", "color", "colour"], + noPm: true, + async process(args, room, user) { + room.say(`/addhtmlbox ${args[0].trim()} | ${Tools.hashColor(Tools.toId(args[0]))}`); + }, +}; diff --git a/showdown/commands/ohko.js b/showdown/commands/ohko.js new file mode 100644 index 0000000..ea48826 --- /dev/null +++ b/showdown/commands/ohko.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + desc: "Uses a One-hit KO move.", + roomRank: "+", + async process(args, room, user) { + return room.say(Tools.random(10) < 3 ? "It's a one-hit KO!" : "The attack missed!"); + }, +}; diff --git a/showdown/commands/overpay.js b/showdown/commands/overpay.js new file mode 100644 index 0000000..acaa126 --- /dev/null +++ b/showdown/commands/overpay.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + desc: "OVERPAY!!", + roomRank: "+", + async process(args, room, user) { + return room.say("/wall __OVERPAY!!__"); + }, +}; diff --git a/showdown/commands/ping.js b/showdown/commands/ping.js new file mode 100644 index 0000000..6334ce8 --- /dev/null +++ b/showdown/commands/ping.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + desc: "Test command.", + roomRank: "+", + async process(args, room, user) { + return room.say("Pong!"); + }, +}; diff --git a/showdown/commands/private/command.js.example b/showdown/commands/private/command.js.example new file mode 100644 index 0000000..04c0e34 --- /dev/null +++ b/showdown/commands/private/command.js.example @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + desc: "Description", + longDesc: "You can really elaborate here!\nFeel free to use linebreaks too.", + usage: "", + aliases: ["alias", "another alias"], + async process(args, room, user) { + // code goes here + return room.say("hello world!"); + }, +}; diff --git a/showdown/config-example.json b/showdown/config-example.json new file mode 100644 index 0000000..0c23b73 --- /dev/null +++ b/showdown/config-example.json @@ -0,0 +1,9 @@ +{ + "username": "YourAmazingUsernameGoesHere", + "password": "", + "developers": [""], + "tournaments": [""], + "rooms": [""], + "commandCharacter": "\\", + "allowMail": false +} diff --git a/showdown/rules/rule.js.example b/showdown/rules/rule.js.example new file mode 100644 index 0000000..09d77bb --- /dev/null +++ b/showdown/rules/rule.js.example @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + rooms: [], + users: [], + async process(message, room, user) { + // Do something! + }, +}; diff --git a/showdown/src/client.js b/showdown/src/client.js new file mode 100644 index 0000000..96e58c4 --- /dev/null +++ b/showdown/src/client.js @@ -0,0 +1,237 @@ +"use strict"; + +const WebSocketClient = require("websocket").client; +const https = require("https"); +const url = require("url"); +const querystring = require("querystring"); + +const RELOGIN_SECONDS = 60; +const MESSAGE_THROTTLE = 600; +const BASE_RECONNECT_SECONDS = 60; + +let server = "play.pokemonshowdown.com"; +if (psConfig.server && psConfig.server !== server) { + server = psConfig.server.includes(".psim.us") ? psConfig.server : `${psConfig.server}.psim.us`; +} +const serverId = "showdown"; +let reconnections = 0; + +const bannedWords = psConfig.bannedWords && Array.isArray(psConfig.bannedWords) ? psConfig.bannedWords.map(x => x.toLowerCase()) : []; + +class Client { + constructor() { + this.serverId = serverId; + this.challstr = ""; + + this.messageQueue = []; + this.messageQueueTimeout = null; + + this.client = new WebSocketClient(); + this.client.on("connect", connection => { + this.connection = connection; + + this.connection.on("message", message => this.onMessage(message)); + this.connection.on("error", error => this.onConnectionError(error)); + this.connection.on("close", (code, description) => this.onConnectionClose(code, description)); + + this.onConnect(); + }); + } + + send(message) { + if (!message || !this.connection || !this.connection.connected) return; + if (this.messageQueueTimeout) { + this.messageQueue.push(message); + return; + } + if (bannedWords.length) { + const lower = message.toLowerCase(); + for (let i = 0, len = bannedWords.length; i < len; i++) { + if (lower.includes(bannedWords[i])) return; + } + } + this.connection.send(message); + this.messageQueueTimeout = setTimeout(() => { + this.messageQueueTimeout = null; + const message = this.messageQueue.shift(); + if (message) this.send(message); + }, MESSAGE_THROTTLE); + } + + onConnectFail(error) { + if (this.connectTimeout) clearTimeout(this.connectTimeout); + if (error) console.log(error.stack); + reconnections++; + const retryTime = BASE_RECONNECT_SECONDS * reconnections; + console.log(`${Tools.showdownText()}Failed to connect to server ${server.brightRed}\n${Tools.showdownText()}(Retrying in ${(retryTime).cyan} seconds${reconnections > 1 ? ` (${reconnections})` : ""})`); + this.connectTimeout = setTimeout(() => this.connect(), retryTime * 1000); + } + + onConnectionError(error) { + console.log(`${Tools.showdownText()}Connection error: ${error.stack}`); + } + + onConnectionClose(code, description) { + if (this.connectTimeout) clearTimeout(this.connectTimeout); + let reconnectTime; + if (this.lockdown) { + console.log(`${Tools.showdownText()}Connection closed: the server restarted!`); + reconnections = 0; + reconnectTime = 15; + } else { + console.log(`${Tools.showdownText()}Connection closed: ${description} (${code})`); + reconnections++; + reconnectTime = BASE_RECONNECT_SECONDS * reconnections; + } + console.log(`Reconnecting in ${reconnectTime.cyan} seconds (${reconnections > 1 ? ` (${reconnections})` : ""}`); + psRooms.destroyRooms(); + psUsers.destroyUsers(); + this.connectTimeout = setTimeout(() => this.connect(), reconnectTime * 1000); + } + + onConnect() { + console.log(`${Tools.showdownText()}Successfully connected to ${server.cyan}`); + } + + connect() { + const options = { + hostname: "play.pokemonshowdown.com", + path: `/crossdomain.php?${querystring.stringify({host: server, path: ""})}`, + method: "GET", + }; + + https.get(options, response => { + response.setEncoding("utf8"); + let data = ""; + response.on("data", chunk => { + data += chunk; + }); + response.on("end", () => { + const configData = data.split("var config = ")[1]; + if (configData) { + let config = JSON.parse(configData.split(";")[0]); + if (typeof config === "string") config = JSON.parse(config); // encoded twice by the server + if (config.host) { + if (config.id) this.serverId = config.id; + this.client.connect(`ws://${config.host === "showdown" ? "sim.smogon.com" : config.host}:${config.port || 8000}/showdown/websocket`); + return; + } + } + console.log(`${Tools.showdownText()}${"Error".brightRed}: failed to get data for server ${server}`); + }); + }).on("error", error => { + console.log(`${Tools.showdownText()}${"Error".brightRed}: ${error.message}`); + }); + + this.connectTimeout = setTimeout(() => this.onConnectFail(), 30 * 1000); + } + login() { + console.log(`${Tools.showdownText()}Logging in to ${server.cyan}`); + const action = url.parse(`https://play.pokemonshowdown.com/~~${this.serverId}/action.php`); + const options = { + hostname: action.hostname, + path: action.pathname, + agent: false, + }; + if (action.port) options.port = parseInt(action.port); + + let postData; + if (psConfig.password) { + options.method = "POST"; + postData = querystring.stringify({ + "act": "login", + "name": psConfig.username, + "pass": psConfig.password, + "challstr": this.challstr, + }); + options.headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": postData.length, + }; + } else { + options.method = "GET"; + options.path += `?${querystring.stringify({ + "act": "getassertion", + "userid": Tools.toId(psConfig.username), + "challstr": this.challstr, + })}`; + } + + const request = https.request(options, response => { + response.setEncoding("utf8"); + let data = ""; + response.on("data", chunk => { + data += chunk; + }); + response.on("end", () => { + if (data === ";") { + console.log(`${Tools.showdownText()}Failed to log in: invalid password`); + process.exit(); + } else if (data.charAt(0) !== "]") { + console.log(`${Tools.showdownText()}Failed to log in: ${data}`); + process.exit(); + } else if (data.startsWith("")) { + console.log(`${Tools.showdownText()}Failed to log in: connection timed out. Trying again in ${RELOGIN_SECONDS.cyan} seconds`); + setTimeout(() => this.login(), RELOGIN_SECONDS * 1000); + return; + } else if (data.includes("heavy load")) { + console.log(`${Tools.showdownText()}Failed to log in: the login server is under heavy load. Trying again in ${RELOGIN_SECONDS.cyan} seconds`); + setTimeout(() => this.login(), RELOGIN_SECONDS * 1000); + return; + } else { + if (psConfig.password) { + const assertion = JSON.parse(data.substr(1)); + if (assertion.actionsuccess && assertion.assertion) { + data = assertion.assertion; + } else { + console.log(`${Tools.showdownText()}Failed to log in: ${JSON.stringify(assertion)}`); + process.exit(); + } + } + this.send(`|/trn ${psConfig.username},0,${data}`); + } + }); + }); + + request.on("error", error => console.log(`${Tools.showdownText()}Login error: ${error.stack}`)); + + if (postData) request.write(postData); + request.end(); + } + + onMessage(message) { + if (message.type !== "utf8" || !message.utf8Data) return; + let room = psRooms.add("lobby"); + if (!message.utf8Data.includes("\n")) return psMessageParser.parse(message.utf8Data, room); + + let lines = message.utf8Data.split("\n"); + if (lines[0].charAt(0) === ">") { + room = psRooms.add(lines[0].substr(1)); + for (let i = 0, len = lines.length; i < len; i++) { + if (lines[i].startsWith("|title|")) { + room.title = lines[i].split("|")[2]; + } + if (lines[i].startsWith("|tier|")) { + room.tier = lines[i].split("|")[2]; + } + } + lines.shift(); + } + for (let i = 0, len = lines.length; i < len; i++) { + if (lines[i].startsWith("|init|")) { + psMessageParser.parse(lines[i], room); + lines = lines.slice(i + 1); + for (let i = 0, len = lines.length; i < len; i++) { + if (lines[i].startsWith("|users|")) { + psMessageParser.parse(lines[i], room); + break; + } + } + return; + } + psMessageParser.parse(lines[i], room); + } + } +} + +module.exports = Client; diff --git a/showdown/src/commandHandler.js b/showdown/src/commandHandler.js new file mode 100644 index 0000000..b8a97f4 --- /dev/null +++ b/showdown/src/commandHandler.js @@ -0,0 +1,270 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +const Commands = require(path.resolve(__dirname, "./commands.js")); +const Dex = require("../../pokemon-showdown/.sim-dist/dex.js").Dex; +// const utilities = require(path.resolve(__dirname, "./utilities.js")); + +const COMMANDS_DIRECTORY = path.resolve(__dirname, "../commands/"); +const CHINESE_COMMANDS_DIRECTORY = path.resolve(__dirname, "../commands/chinese/"); +const DEV_COMMANDS_DIRECTORY = path.resolve(__dirname, "../commands/dev/"); +const DEX_COMMANDS_DIRECTORY = path.resolve(__dirname, "../commands/dex/"); +const PRIVATE_COMMANDS_DIRECTORY = path.resolve(__dirname, "../commands/private/"); + +// const databaseDirectory = path.resolve(__dirname, "../../databases"); + +class CommandHandler { + constructor() { + this.commands = []; + this.chineseCommands = []; + } + + async init(isReload) { + console.log(`${Tools.showdownText()}${isReload ? "Rel" : "L"}oading commands...`); + await Promise.all([ + this.loadDirectory(COMMANDS_DIRECTORY, Commands.ShowdownCommand, "Bot", isReload), + this.loadDirectory(CHINESE_COMMANDS_DIRECTORY, Commands.ChineseCommand, "Chinese", isReload), + this.loadDirectory(DEV_COMMANDS_DIRECTORY, Commands.DevCommand, "Dev", isReload), + this.loadDirectory(DEX_COMMANDS_DIRECTORY, Commands.DexCommand, "Dex", isReload), + this.loadDirectory(PRIVATE_COMMANDS_DIRECTORY, Commands.PrivateCommand, "Private", isReload), + ]); + for (let i = 0; i < this.chineseCommands.length; i++) { + for (let j = 0; j < this.chineseCommands.length; j++) { + if (i === j || this.chineseCommands[j].commandType === "ChineseCommand" || this.chineseCommands[i].name !== this.chineseCommands[j].name) continue; + this.chineseCommands.splice(j, 1); + } + } + } + + loadDirectory(directory, Command, type, isReload) { + console.log(`${Tools.showdownText()}${isReload ? "Rel" : "L"}oading ${type.cyan} commands...`); + return new Promise((resolve, reject) => { + fs.readdir(directory, (err, files) => { + if (err) { + reject(`Error reading commands directory: ${err}`); + } else if (!files) { + reject(`No files in directory ${directory}`); + } else { + for (let name of files) { + if (name.endsWith(".js")) { + try { + name = name.slice(0, -3); // remove extention + const command = new Command(name, require(`${directory}/${name}.js`)); + + if (command.commandType !== "ChineseCommand") this.commands.push(command); + this.chineseCommands.push(command); + /*fs.readdir(databaseDirectory, (err, dbs) => { + for (let id of dbs) { + if (id.endsWith(".json")) { + id = id.slice(0, -5); // remove extention + if (client.guilds.cache.get(id) !== undefined) { + utilities.populateDb(id, name, type); + } + } + } + });*/ + if (!(isReload)) console.log(`${Tools.showdownText()}${isReload ? "Rel" : "L"}oaded command ${type === "Private" ? (`${name.charAt(0)}*****`).green : name.green}`); + } catch (e) { + console.log(`${Tools.showdownText()}${"CommandHandler loadDirectory() error: ".brightRed}${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); + console.log(e.stack); + } + } + } + console.log(`${Tools.showdownText()}${type.cyan} commands ${isReload ? "rel" : "l"}oaded!`); + resolve(); + } + }); + }); + } + + get(name, list) { + const commandsList = list ? list : this.commands; + for (const command of commandsList) { + if ([command.name, ...command.aliases].includes(Tools.toId(name))) return command; + } + throw new Error(`commandHandler error: Command "${name}" not found!`); + } + + async executeCommand(message, room, user, time) { + let passDex = Dex; + message = message.substr(1); // Remove command character + + const spaceIndex = message.indexOf(" "); + let args = []; + let cmd = ""; + if (spaceIndex !== -1) { + cmd = message.substr(0, spaceIndex); + args = message.substr(spaceIndex + 1).split(","); + } else { + cmd = message; + } + + const commandsList = room.id === "chinese" ? this.chineseCommands : this.commands; + for (let i = 0; i < commandsList.length; i++) { + const command = commandsList[i]; + if (command.trigger(cmd)) { + // Permissions checking + if (!user.isDeveloper()) { + if (command.developerOnly) return user.say("You do not have permission to use this command!"); + if (command.roomRank && !(user.hasRoomRank(room, command.roomRank) || user.hasGlobalRank(command.roomRank))) return user.say(`You do not have permission to use \`\`${psConfig.commandCharacter}${command.name}\`\` in <<${room.id}>>.`); + if (command.globalRank && !user.hasGlobalRank(command.globalRank)) return user.say(`You do not have permission to use \`\`${psConfig.commandCharacter}${command.name}\`\` in <<${room.id}>>.`); + if (command.rooms && !command.rooms.includes(room.id)) return; + if (command.noPm && room.isPm()) return user.say(`\`\`${psConfig.commandCharacter}${command.name}\`\` is not available in PMs!`); + if (command.pmOnly && !room.isPm()) return user.say(`\`\`${psConfig.commandCharacter}${command.name}\`\` is only available in PMs!`); + } + + if (command.commandType === "DexCommand") { + for (let i = 0; i < args.length; i++) { + if (["lgpe", "gen7", "gen6", "gen5", "gen4", "gen3", "gen2", "gen1", "usum", "sm", "oras", "xy", "bw2", "bw", "hgss", "dppt", "adv", "rse", "frlg", "gsc", "rby"].includes(Tools.toId(args[i]))) { + switch (Tools.toId(args[i])) { + case "lgpe": + passDex = Dex.mod("lgpe"); + break; + case "gen7": + case "usum": + case "sm": + passDex = Dex.mod("gen7"); + break; + case "gen6": + case "oras": + case "xy": + passDex = Dex.mod("gen6"); + break; + case "gen5": + case "bw2": + case "bw": + passDex = Dex.mod("gen5"); + break; + case "gen4": + case "hgss": + case "dppt": + passDex = Dex.mod("gen4"); + break; + case "gen3": + case "adv": + case "rse": + case "frlg": + passDex = Dex.mod("gen3"); + break; + case "gen2": + case "gsc": + passDex = Dex.mod("gen2"); + break; + case "gen1": + case "rby": + case "rbyg": + passDex = Dex.mod("gen1"); + break; + default: + passDex = Dex.mod("gen7"); + } + args.splice(i, 1); + break; + } + } + } + + const hrStart = process.hrtime(); + console.log(`${Tools.showdownText()}Executing command: ${command.name.cyan}`); + try { + if (command.commandType === "DexCommand") { + await command.execute(args, room, user, passDex); + } else { + await command.execute(args, room, user); + } + } catch (e) { + let stack = e.stack; + stack += "Additional information:\n"; + stack += `Command = ${command.name}\n`; + stack += `Args = ${args}\n`; + stack += `Time = ${new Date(time).toLocaleString()}\n`; + stack += `User = ${user.name}\n`; + stack += `Room = ${room instanceof psUsers.User ? "in PM" : room.id}`; + console.log(stack); + } + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.showdownText()}Executed command: ${command.name.green} in ${timeString}`); + } + } + } + + helpCommand(message, room, user, time) { + const hrStart = process.hrtime(); + const commandsList = room.id === "chinese" ? this.chineseCommands : this.commands; + console.log(`${Tools.showdownText()}Executing command: ${"help".cyan}`); + const botCommands = []; + const devCommands = []; + const dexCommands = []; + let sendMsg = []; + + if (!(message.trim().includes(" "))) { + user.say(`List of commands; use \`\`${psConfig.commandCharacter}help \`\` for more information:`); + for (let i = 0; i < commandsList.length; i++) { + const command = commandsList[i]; + if (!command.disabled && command.commandType !== "PrivateCommand") { + const cmdText = `${psConfig.commandCharacter}${command.name}${command.desc ? ` - ${command.desc}` : ""}`; + switch (command.commandType) { + case "BotCommand": + botCommands.push(cmdText); + break; + case "DevCommand": + devCommands.push(cmdText); + break; + case "DexCommand": + dexCommands.push(cmdText); + break; + } + } + } + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.showdownText()}Executed command: ${"help".green} in ${timeString}`); + if (botCommands.length > 0) { + user.say("**Bot commands**"); + for (const line of botCommands) user.say(line); + } + if (devCommands.length > 0) { + user.say("**Dev Commands**"); + for (const line of devCommands) user.say(line); + } + if (dexCommands.length > 0) { + user.say("**Dex Commands**"); + for (const line of dexCommands) user.say(line); + } + return true; + } + const lookup = Tools.toId(message.split(" ")[1]); + let command; + let matched = false; + console.log(commandsList); + for (let i = 0; i < commandsList.length; i++) { + if (matched) break; + command = commandsList[i]; + if (command.name === lookup || (command.aliases && command.aliases.includes(lookup))) matched = true; + } + + if (!matched) return user.say(`No command "${lookup}" found!`); + + sendMsg = [ + `Help for: ${command.name}`, + `Usage: \`\`${psConfig.commandCharacter}${command.name}${command.usage.length > 0 ? ` ${command.usage}` : ""}\`\``, + `Description: ${command.longDesc}`, + `${command.aliases.length > 0 ? `Aliases: ${command.aliases.join(", ")}` : ""}`, + ]; + if (command.options) { + for (let i = 0; i < command.options.length; i++) { + sendMsg.push(`${command.options[i].toString()}: ${command.options[i].desc}`); + } + } + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.showdownText()}Executed command: ${"help".green} in ${timeString}`); + for (const line of sendMsg) user.say(line); + return; + } +} + +module.exports = CommandHandler; diff --git a/showdown/src/commands.js b/showdown/src/commands.js new file mode 100644 index 0000000..b63634f --- /dev/null +++ b/showdown/src/commands.js @@ -0,0 +1,128 @@ +"use strict"; + +class Command { + constructor(name, cmd) { + this.name = name.toLowerCase(); + this.usage = cmd.usage || ""; + this.desc = cmd.desc || "No description."; + this.longDesc = cmd.longDesc || this.desc; + this.aliases = cmd.aliases || []; + this.disabled = cmd.disabled || false; + this.developerOnly = cmd.developerOnly || false; + this.roomRank = cmd.roomRank || null; + this.globalRank = cmd.globalRank || null; + this.rooms = cmd.rooms || false; + this.process = cmd.process; + this.noPm = cmd.noPm || false; + this.pmOnly = cmd.pmOnly || false; + this.commandType = "Command"; + + if (Array.isArray(this.longDesc)) { + this.longDesc = this.longDesc.join("\n"); + } + } + + trigger(cmd = "") { + if (this.disabled) { + return false; + } + cmd = cmd.replace(/\s/gi, "").toLowerCase(); + if (cmd === `${this.name}`) { + return true; + } + for (let i = 0; i < this.aliases.length; i++) { + if (cmd === `${this.aliases[i]}`) { + return true; + } + } + return false; + } + + execute(args, room, user) { + return this.process(args, room, user); + } + + toString() { + return `${psConfig.commandCharacter}${this.name} ${this.usage}`; + } +} + +class DevCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "DevCommand"; + } + + execute(args, room, user) { + if (this.disabled) { + return; + } + return this.process(args, room, user); + } +} + +class PrivateCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "PrivateCommand"; + } + + execute(args, room, user) { + if (this.disabled) { + return; + } + return this.process(args, room, user); + } +} + +class ChineseCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "ChineseCommand"; + } + + execute(args, room, user) { + if (this.disabled) { + return; + } + return this.process(args, room, user); + } +} + +class DexCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "DexCommand"; + } + + execute(args, room, user, dex) { + if (this.disabled) { + return; + } + if (!dex) { + return; + } + return this.process(args, room, user, dex); + } +} + +class ShowdownCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "BotCommand"; + } + + execute(args, room, user) { + if (this.disabled) { + return; + } + return this.process(args, room, user); + } +} + +module.exports.Command = Command; +module.exports.DevCommand = DevCommand; +module.exports.DexCommand = DexCommand; +module.exports.ShowdownCommand = ShowdownCommand; +module.exports.ChineseCommand = ChineseCommand; +module.exports.PrivateCommand = PrivateCommand; diff --git a/showdown/src/groups.json b/showdown/src/groups.json new file mode 100644 index 0000000..ca0176e --- /dev/null +++ b/showdown/src/groups.json @@ -0,0 +1 @@ +{"\u203d":0,"!":0," ":0,"\u2606":1,"+":1,"%":2,"@":3,"*":3,"#":4,"&":5,"~":6} diff --git a/showdown/src/messageParser.js b/showdown/src/messageParser.js new file mode 100644 index 0000000..7631f50 --- /dev/null +++ b/showdown/src/messageParser.js @@ -0,0 +1,531 @@ +"use strict"; + +const path = require("path"); +const Rules = require(path.resolve(__dirname, "./rules.js")); +const RULES_DIRECTORY = path.resolve(__dirname, "../rules/"); + +const utilities = require("./utilities.js"); + +class Context { + constructor(target, room, user, command, originalCommand, time) { + this.target = target ? target.trim() : ""; + this.room = room; + this.user = user; + this.command = command; + this.originalCommand = originalCommand; + this.time = time || Date.now(); + } + + say(text) { + this.room.say(text); + } + + pm(user, message) { + if (typeof user === "string") user = psUsers.add(user); + user.say(message); + } +} + +exports.Context = Context; + +class MessageParser { + constructor() { + this.formatsList = []; + this.formatsData = {}; + this.globalContext = new Context("", psRooms.globalRoom, psUsers.self, "", ""); + this.rules = []; + this.tourRulesListeners = {}; + } + + async init(isReload) { + console.log(`${Tools.showdownText()}${isReload ? "Rel" : "L"}oading rules...`); + await Promise.all([ + this.loadDirectory(RULES_DIRECTORY, Rules.Rule, isReload), + ]); + } + + loadDirectory(directory, Rule, isReload) { + console.log(`${Tools.showdownText()}${isReload ? "Rel" : "L"}oading ${"Message Parser".cyan} rules...`); + return new Promise((resolve, reject) => { + fs.readdir(directory, (err, files) => { + if (err) { + reject(`Error reading rules directory: ${err}`); + } else if (!files) { + reject(`No files in directory ${directory}`); + } else { + for (let name of files) { + if (name.endsWith(".js")) { + try { + name = name.slice(0, -3); // remove extention + const rule = new Rule(name, require(`${directory}/${name}.js`)); + this.rules.push(rule); + if (!(isReload)) console.log(`${Tools.showdownText()}${isReload ? "Rel" : "L"}oaded rule ${name.green}`); + } catch (e) { + console.log(`${Tools.showdownText()}${"MessageParser loadDirectory() error: ".brightRed} ${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); + } + } + } + console.log(`${Tools.showdownText()}${"Message Parser".cyan} rules ${isReload ? "rel" : "l"}oaded!`); + resolve(); + } + }); + }); + } + + get(name) { + for (const rule of this.rules) { + if ([rule.name].includes(Tools.toId(name))) return rule; + } + throw new Error(`messageParser error: Rule "${name}" not found!`); + } + + process(message, room, user) { + for (let i = 0; i < this.rules.length; i++) { + const rule = this.rules[i]; + if (rule.rooms.length > 0 && !rule.rooms.includes(room.id)) continue; + if (rule.users.length > 0 && !rule.users.includes(user.id)) continue; + rule.execute(message, room, user); + } + } + + parse(message, room) { + const splitMessage = message.split("|").slice(1); + const messageType = splitMessage[0]; + splitMessage.shift(); + + switch (messageType) { + case "challstr": + psClient.challstr = splitMessage.join("|"); + psClient.login(); + break; + case "updateuser": + const parsedUsername = Tools.parseUsernameText(splitMessage[0]); + if (Tools.toId(parsedUsername.username) !== psUsers.self.id) return; + + if (psClient.connectTimeout) clearTimeout(psClient.connectTimeout); + if (splitMessage[1] !== "1") { + console.log(`${Tools.showdownText()}${"Failed to log in.".brightRed}`); + process.exit(); + } + + console.log(`${Tools.showdownText()}Successfully logged in!`); + if (psConfig.rooms) { + if (!(psConfig.rooms instanceof Array)) throw new Error("psConfig.rooms must be an array"); + for (const room of psConfig.rooms) { + psClient.send(`|/join ${room}`); + } + } + if (psConfig.avatar) psClient.send(`|/avatar ${psConfig.avatar}`); + break; + case "init": + room.onJoin(psUsers.self, " "); + console.log(`${Tools.showdownText()}Joined room: ${room.id.green}`); + if (!room.id.includes("battle-")) utilities.checkForDb(room.id, "{}"); + break; + case "noinit": + console.log(`${Tools.showdownText()}Could not join room: ${room.id.brightRed}`); + psRooms.destroy(room); + break; + case "deinit": + psRooms.destroy(room); + break; + case "users": + if (splitMessage[0] === "0") return; + const users = splitMessage[0].split(","); + for (let i = 1, len = users.length; i < len; i++) { + const parsedUsername = Tools.parseUsernameText(users[i].substr(1)); + const user = psUsers.add(parsedUsername.username); + const rank = users[i].charAt(0); + room.users.set(user, rank); + user.rooms.set(room, rank); + } + break; + case "c:": + const user = psUsers.get(splitMessage[1]); + const rank = splitMessage[1].charAt(0); + if (user.rooms.get(room) !== rank) user.rooms.set(room, rank); + let message = splitMessage.slice(2).join("|"); + message = message.trim(); + const time = parseInt(splitMessage[0]) * 1000; + // No need for pm checking + // this.process(message, room, user); + if (message.charAt(0) === psConfig.commandCharacter) { + try { + if (Tools.toId(message.split(" ")[0]) === "help") { + psCommandHandler.helpCommand(message, room, user, time); + } else { + psCommandHandler.executeCommand(message, room, user, time); + } + } catch (e) {} + } + break; + case "pm": { + const user = psUsers.add(splitMessage[0]); + if (!user) return; + if (user.id === psUsers.self.id) return; + user.globalRank = (splitMessage[0][0]); + try { + if (Tools.toId(splitMessage[2].split(" ")[0]) === "help") { + psCommandHandler.helpCommand(splitMessage.slice(2).join("|"), user, user); + } else { + psCommandHandler.executeCommand(splitMessage.slice(2).join("|"), user, user); + } + } catch (e) {} + break; + } + case "N": + case "n": { + const user = psUsers.add(splitMessage[1].split("@")[0]); + if (!user) return; + const zn = splitMessage[0].replace(/^./, ""); + const n = zn.indexOf("@"); + const text = zn.substring(0, n !== -1 ? n : splitMessage[0].length); + let status = zn.substring(n !== -1 ? n + 1 : zn.length, zn.length); + if (status.charAt(0) === "!") { + user.away = true; + status = status.substr(1); + } else { user.away = false; } + user.status = status; + if (status.length > 6) { + console.log(`${user.id}, ${room.id}: ${status}`); + } + splitMessage[0] = splitMessage[0][0] + text; + if (!user.alts.includes(Tools.toId(splitMessage[0]))) { + user.alts.push(Tools.toId(splitMessage[0])); + } + if (!user.alts.includes(Tools.toId(splitMessage[1]))) { + user.alts.push(Tools.toId(splitMessage[1])); + } + utilities.checkForDb("alts", "{}"); + const alts = Storage.getDatabase("alts"); + let match0 = false; + let match1 = false; + for (let id = 0; id < alts.length; id++) { + if (alts[id].includes(Tools.toId(splitMessage[0]))) { + match0 = true; + } + if (alts[id].includes(Tools.toId(splitMessage[1]))) { + match1 = true; + } + } + if (!match0) { + if (!alts[user.id]) alts[user.id] = []; + if (!alts[user.id].includes(Tools.toId(splitMessage[0]))) alts[user.id].push(Tools.toId(splitMessage[0])); + } + if (!match1) { + if (!alts[user.id]) alts[user.id] = []; + if (!alts[user.id].includes(Tools.toId(splitMessage[1]))) alts[user.id].push(Tools.toId(splitMessage[1])); + } + Storage.exportDatabase("alts"); + room.onRename(user, splitMessage[0]); + utilities.checkForDb("mail", "{}"); + const db = Storage.getDatabase("mail"); + if (user.id in db) { + const mail = db[user.id]; + for (let i = 0, len = mail.length; i < len; i++) { + user.say(`[${Tools.toDurationString(Date.now() - mail[i].time)} ago] **${mail[i].from}** said: ${mail[i].text}`); + } + delete db[user.id]; + Storage.exportDatabase("mail"); + } + break; + } + case "J": + case "j": { + // remove first character (so that we don't get false positives for Moderators) + const zn = splitMessage[0].replace(/^./, ""); + // search for the @ character and delete the status that comes after + const n = zn.indexOf("@"); + const text = zn.substring(0, n !== -1 ? n : splitMessage[0].length); + splitMessage[0] = splitMessage[0][0] + text; + const user = psUsers.add(splitMessage[0]); + if (!user) return; + // get the status from what comes after the @ + let status = zn.substring(n !== -1 ? n + 1 : zn.length, zn.length); + if (status.charAt(0) === "!") { + user.away = true; + status = status.substr(1); + } else { + user.away = false; + } + user.status = status; + room.onJoin(user, splitMessage[0].charAt(0)); + utilities.checkForDb("mail", "{}"); + const db = Storage.getDatabase("mail"); + if (user.id in db) { + const mail = db[user.id]; + for (let i = 0, len = mail.length; i < len; i++) { + user.say(`[${Tools.toDurationString(Date.now() - mail[i].time)} ago] **${mail[i].from}** said: ${mail[i].text}`); + } + delete db[user.id]; + Storage.exportDatabase("mail"); + } + break; + } + case "formats": { + this.formatsList = splitMessage.slice(); + this.parseFormats(); + break; + } + case "tournament": { + if (!psConfig.tournaments || !psConfig.tournaments.includes(room.id)) return; + switch (splitMessage[0]) { + case "create": { + const format = Tools.getFormat(splitMessage[1]); + if (!format) throw new Error(`Unknown format used in tournament (${splitMessage[1]})`); + room.tour = Tournaments.createTournament(room, format, splitMessage[2]); + if (splitMessage[3]) room.tour.playerCap = parseInt(splitMessage[3]); + if (room.id === "chinese") { + // Apparently `format.gen` sometimes === 0, so we have to extract it manually from the id + const gen = parseInt(format.id.substring(3, 4)) - 1; + const genStr = [ + "【第一代】", // Gen 1 + "【第二代】", // Gen 2 + "【第三代】", // Gen 3 + "【第四代】", // Gen 4 + "【第五代】", // Gen 5 + "【第六代】", // Gen 6 + "【第七代】", // Gen 7 + "【第八代】", // Gen 8 + ][gen]; + + let formatName = format.name.substring(format.name.indexOf("] ") + 2); + + switch (format.id.substring(4)) { + case "challengecup1v1": + case "challengecup": + formatName = "挑战杯"; + break; + + case "challengecup2v2": + formatName = "双打挑战杯"; + break; + + case "hackmonscup": + formatName = "BH杯"; + break; + + case "doubleshackmonscup": + formatName = "双打BH杯"; + break; + + case "monotyperandombattle": + formatName = "同属性随机对战"; + break; + + case "battlefactory": + formatName = "战斗工厂"; + break; + + case "randombattle": + formatName = "随机对战"; + break; + + case "randomdoublesbattle": + formatName = "双打随机对战"; + break; + + case "cap1v1": + formatName = "自创宝可梦杯"; + break; + + case "bssfactory": + formatName = "63工厂"; + break; + } + + const elim = splitMessage[2] === "Double Elimination" ? "(两条命)" : ""; + + const name = `${genStr}${formatName}${elim}`; + room.say(`/tour name ${name}`); + room.say("/tour autostart on"); + } + break; + } + case "update": { + const data = JSON.parse(splitMessage.slice(1).join("|")); + if (!data || !(data instanceof Object)) return; + if (!room.tour) { + const format = Tools.getFormat(data.teambuilderFormat) || Tools.getFormat(data.format); + if (!format) throw new Error(`Unknown format used in tournament (${(data.teambuilderFormat || data.format)})`); + room.tour = Tournaments.createTournament(room, format, data.generator); + room.tour.started = true; + } + Object.assign(room.tour.updates, data); + break; + } + case "updateEnd": + if (room.tour) room.tour.update(room); + break; + case "end": { + const data = JSON.parse(splitMessage.slice(1).join("|")); + if (!data || !(data instanceof Object)) return; + if (!room.tour) { + const format = Tools.getFormat(data.teambuilderFormat) || Tools.getFormat(data.format); + if (!format) throw new Error(`Unknown format used in tournament (${(data.teambuilderFormat || data.format)})`); + room.tour = Tournaments.createTournament(room, format, data.generator); + room.tour.started = true; + } + Object.assign(room.tour.updates, data); + room.tour.update(room); + room.tour.end(room); + break; + } + case "forceend": + if (room.tour) room.tour.end(room); + break; + case "join": + if (room.tour) room.tour.addPlayer(splitMessage[1]); + break; + case "leave": + if (room.tour) room.tour.removePlayer(splitMessage[1]); + break; + case "disqualify": + if (room.tour) room.tour.removePlayer(splitMessage[1]); + break; + case "start": + if (room.tour) room.tour.start(); + break; + case "battlestart": + if (room.tour && !room.tour.isRoundRobin && room.tour.generator === 1 && room.tour.getRemainingPlayerCount() === 2) { + room.say(`/wall Final battle of ${room.tour.format.name} tournament: <<${splitMessage[3].trim()}>>`); + } + break; + } + break; + } + /* case "html": { + if (!this.tourRulesListeners || !this.tourRulesListeners[room.id]) this.tourRulesListeners[room.id] = false; + if (room.id !== "chinese") { + this.tourRulesListeners[room.id] = true; + return; + } + for (const message of splitMessage) { + if (!message.includes("This tournament includes:")) return; + if (!message.includes("Removed rules")) return; + if (!message.includes("- teampreview")) return; + + const gen = parseInt(room.tour.format.id.substring(3, 4)) - 1; + const genStr = [ + "【第一代】", // Gen 1 + "【第二代】", // Gen 2 + "【第三代】", // Gen 3 + "【第四代】", // Gen 4 + "【第五代】", // Gen 5 + "【第六代】", // Gen 6 + "【第七代】", // Gen 7 + "【第八代】", // Gen 8 + ][gen]; + + let formatName = room.tour.format.name.substring(room.tour.name.indexOf("] ") + 2); + + switch (room.tour.format.id.substring(4)) { + case "challengecup1v1": + case "challengecup": + formatName = "挑战杯"; + break; + + case "challengecup2v2": + formatName = "双打挑战杯"; + break; + + case "hackmonscup": + formatName = "BH杯"; + break; + + case "doubleshackmonscup": + formatName = "双打BH杯"; + break; + + case "monotyperandombattle": + formatName = "同属性随机对战"; + break; + + case "battlefactory": + formatName = "战斗工厂"; + break; + + case "randombattle": + formatName = "随机对战"; + break; + + case "randomdoublesbattle": + formatName = "双打随机对战"; + break; + + case "cap1v1": + formatName = "自创宝可梦杯"; + break; + + case "bssfactory": + formatName = "63工厂"; + break; + } + + const elim = room.tour.generator === 2 ? "(两条命)" : ""; + + const name = `${genStr}${formatName}${elim}(无队伍预览)`; + room.say(`/tour name ${name}`); + this.tourRulesListeners[room.id] = true; + } + } */ + } + } + + parseFormats() { + if (!this.formatsList.length) return; + this.formatsData = {}; + let isSection = false; + let section = ""; + for (let i = 0, len = this.formatsList.length; i < len; i++) { + if (isSection) { + section = this.formatsList[i]; + isSection = false; + } else if (this.formatsList[i] === ",LL") { + continue; + } else if (this.formatsList[i] === "" || (this.formatsList[i].charAt(0) === "," && !isNaN(parseInt(this.formatsList[i].substr(1))))) { + isSection = true; + } else { + let name = this.formatsList[i]; + let searchShow = true; + let challengeShow = true; + let tournamentShow = true; + const lastCommaIndex = name.lastIndexOf(","); + const code = lastCommaIndex >= 0 ? parseInt(name.substr(lastCommaIndex + 1), 16) : NaN; + if (!isNaN(code)) { + name = name.substr(0, lastCommaIndex); + if (!(code & 2)) searchShow = false; + if (!(code & 4)) challengeShow = false; + if (!(code & 8)) tournamentShow = false; + } else { + // Backwards compatibility: late 0.9.0 -> 0.10.0 + if (name.substr(name.length - 2) === ",#") { // preset teams + name = name.substr(0, name.length - 2); + } + if (name.substr(name.length - 2) === ",,") { // search-only + challengeShow = false; + name = name.substr(0, name.length - 2); + } else if (name.substr(name.length - 1) === ",") { // challenge-only + searchShow = false; + name = name.substr(0, name.length - 1); + } + } + const id = Tools.toId(name); + if (!id) continue; + this.formatsData[id] = { + name: name, + id: id, + section: section, + searchShow: searchShow, + challengeShow: challengeShow, + tournamentShow: tournamentShow, + playable: tournamentShow || ((searchShow || challengeShow) && tournamentShow !== false), + }; + } + } + + Tools.FormatCache.clear(); + } +} + +exports.MessageParser = new MessageParser(); diff --git a/showdown/src/room-tournament.js b/showdown/src/room-tournament.js new file mode 100644 index 0000000..00a651a --- /dev/null +++ b/showdown/src/room-tournament.js @@ -0,0 +1,251 @@ +/** + * Room Tournament + * Cassius - https://github.com/sirDonovan/Cassius + * + * This file contains the tournament and tournament-player classes + * + * @license MIT license + */ + +"use strict"; + +/**@type {{[k: string]: number}} */ +const generators = { + "Single": 1, + "Double": 2, + "Triple": 3, + "Quadruple": 4, + "Quintuple": 5, + "Sextuple": 6, +}; + +class TournamentPlayer { + /** + * @param {User | string} user + */ + constructor(user) { + if (typeof user === "string") { + this.name = user; + this.id = Tools.toId(user); + } else { + this.name = user.name; + this.id = user.id; + } + this.losses = 0; + this.eliminated = false; + } + + /** + * @param {string} message + */ + say(message) { + Users.add(this.name).say(message); + } +} + +exports.TournamentPlayer = TournamentPlayer; + +class Tournament { + /** + * @param {Room} room + * @param {any} format + * @param {string} generator + */ + constructor(room, format, generator) { + this.room = room; + this.format = format; + this.name = format.name; + this.generator = 1; + const generatorName = generator.split(" ")[0]; + if (generatorName in generators) { + this.generator = generators[generatorName]; + } else { + const generatorNumber = parseInt(generator.split("-tuple")[0]); + if (!isNaN(generatorNumber)) this.generator = generatorNumber; + } + this.isRoundRobin = Tools.toId(generator).includes("roundrobin"); + /**@type {{[k: string]: TournamentPlayer}} */ + this.players = {}; + this.playerCount = 0; + this.totalPlayers = 0; + this.started = false; + this.ended = false; + this.playerCap = Tournaments.defaultCap; + this.createTime = Date.now(); + this.startTime = 0; + this.maxRounds = 6; + /**@type {AnyObject} */ + this.info = {}; + /**@type {AnyObject} */ + this.updates = {}; + } + + start() { + this.startTime = Date.now(); + this.started = true; + let maxRounds = 0; + let rounds = Math.ceil((Math.log(this.playerCount) / Math.log(2))); + let generator = this.generator; + while (generator > 0) { + maxRounds += rounds; + rounds--; + generator--; + } + this.maxRounds = maxRounds; + this.totalPlayers = this.playerCount; + } + + /** + * @param {User | string} user + * @return {TournamentPlayer} + */ + addPlayer(user) { + const id = Tools.toId(user); + if (id in this.players) return this.players[id]; + const player = new TournamentPlayer(user); + this.players[id] = player; + this.playerCount++; + return player; + } + + /** + * @param {User | string} user + */ + removePlayer(user) { + const id = Tools.toId(user); + if (!(id in this.players)) return; + if (this.started) { + this.players[id].eliminated = true; + } else { + delete this.players[id]; + this.playerCount--; + } + } + + /** + * @param {User} user + * @param {string} oldName + */ + renamePlayer(user, oldName) { + const oldId = Tools.toId(oldName); + if (!(oldId in this.players)) return; + const player = this.players[oldId]; + player.name = user.name; + if (player.id === user.id || user.id in this.players) return; + player.id = user.id; + this.players[user.id] = player; + delete this.players[oldId]; + } + + /** + * @return {number} + */ + getRemainingPlayerCount() { + let count = 0; + for (const i in this.players) { + if (!this.players[i].eliminated) count++; + } + return count; + } + + update(room) { + Object.assign(this.info, this.updates); + if (this.updates.bracketData && this.started) this.updateBracket(); + if (this.updates.format) { + const format = Tools.getFormat(this.updates.format); + if (format) { + this.name = format.name; + } else { + this.name = this.updates.format; + } + } + this.updates = {}; + if (!psMessageParser.tourRulesListeners[room.id]) psMessageParser.tourRulesListeners[room.id] = false; + if (!psMessageParser.tourRulesListeners[room.id]) room.say("/tour rules"); + } + + updateBracket() { + const data = this.info.bracketData; + /**@type {{[k: string]: string}} */ + const players = {}; + /**@type {{[k: string]: number}} */ + const losses = {}; + if (data.type === "tree") { + if (data.rootNode) { + const queue = [data.rootNode]; + while (queue.length > 0) { + const node = queue.shift(); + if (!node || !node.children) break; + + if (node.children[0] && node.children[0].team) { + const userA = Tools.toId(node.children[0].team); + if (!players[userA]) players[userA] = node.children[0].team; + if (node.children[1] && node.children[1].team) { + const userB = Tools.toId(node.children[1].team); + if (!players[userB]) players[userB] = node.children[1].team; + if (node.state === "finished") { + if (node.result === "win") { + if (!losses[userB]) losses[userB] = 0; + losses[userB]++; + } else if (node.result === "loss") { + if (!losses[userA]) losses[userA] = 0; + losses[userA]++; + } + } + } + } + + node.children.forEach(/**@param {any} child*/ function (child) { + queue.push(child); + }); + } + } + } else if (data.type === "table") { + if (data.tableHeaders && data.tableHeaders.cols) { + for (let i = 0, len = data.tableHeaders.cols.length; i < len; i++) { + const player = Tools.toId(data.tableHeaders.cols[i]); + if (!players[player]) players[player] = data.tableHeaders.cols[i]; + } + } + } + if (!this.playerCount) { + const len = Object.keys(players).length; + let maxRounds = 0; + let rounds = Math.ceil((Math.log(len) / Math.log(2))); + let generator = this.generator; + while (generator > 0) { + maxRounds += rounds; + rounds--; + generator--; + } + this.maxRounds = maxRounds; + this.totalPlayers = len; + this.playerCount = len; + } + + // clear users who are now guests (currently can't be tracked) + for (const i in this.players) { + if (!(i in players)) delete this.players[i]; + } + + for (const i in players) { + const player = this.addPlayer(players[i]); + if (!player || player.eliminated) continue; + if (losses[i] && player.losses < losses[i]) { + player.losses = losses[i]; + if (player.losses >= this.generator) { + player.eliminated = true; + continue; + } + } + } + } + + end(room) { + this.ended = true; + if (psMessageParser.tourRulesListeners[room.id]) delete psMessageParser.tourRulesListeners[room.id]; + delete this.room.tour; + } +} + +exports.Tournament = Tournament; diff --git a/showdown/src/rooms.js b/showdown/src/rooms.js new file mode 100644 index 0000000..87179c0 --- /dev/null +++ b/showdown/src/rooms.js @@ -0,0 +1,107 @@ +"use strict"; + +class Room { + constructor(id) { + this.id = id; + this.clientId = id === "lobby" ? "" : id; + this.users = new Map(); + this.listeners = {}; + this.game = null; + this.tour = null; + + this.title = ""; + this.tier = ""; + } + + onJoin(user, rank) { + this.users.set(user, rank); + user.rooms.set(this, rank); + } + + onLeave(user, rank) { + this.users.delete(user); + this.rooms.delete(this); + } + + onRename(user, newName) { + const rank = newName.charAt(0); + newName = Tools.toName(newName); + const id = Tools.toId(newName); + const oldName = user.name; + if (id === user.id) { + user.name = newName; + } else { + delete psUsers.users[user.id]; + if (psUsers.users[id]) { + user = psUsers.users[id]; + user.name = newName; + } else { + user.name = newName; + user.id = id; + psUsers.users[id] = user; + } + } + this.users.set(user, rank); + this.users.set(this, rank); + if (this.game) this.game.renamePlayer(user, oldName); + if (this.tour) this.tour.renamePlayer(user, oldName); + } + + say(message, skipNormalization) { + if (!(skipNormalization)) message = Tools.normalizeMessage(message, this); + if (!(message)) return; + psClient.send(`${this.id}|${message}`); + } + + isPm() { + return this instanceof psUsers.User; + } + + on(message, listener) { + message = Tools.normalizeMessage(message, this); + if (!(message)) return; + this.listeners[Tools.toId(message)] = listener; + } +} +exports.Room = Room; + +class Rooms { + constructor() { + this.rooms = {}; + this.Room = Room; + this.globalRoom = this.add("global"); + } + + get(id) { + if (id instanceof Room) return id; + return this.rooms[id]; + } + + add(id) { + let room = this.get(id); + if (!room) { + room = new Room(id); + this.rooms[id] = room; + } + return room; + } + + destroy(id) { + const room = this.get(id); + if (!room) return; + if (room.game) room.game.forceEnd(); + if (room.tour) room.tour.end(); + room.users.forEach(function (value, user) { + user.rooms.delete(room); + }); + delete this.rooms[room.id]; + } + + destroyRooms() { + for (const i in this.rooms) { + this.destroy(i); + } + } +} + +exports.Rooms = new Rooms(); diff --git a/showdown/src/rules.js b/showdown/src/rules.js new file mode 100644 index 0000000..768c33c --- /dev/null +++ b/showdown/src/rules.js @@ -0,0 +1,16 @@ +"use strict"; + +class Rule { + constructor(name, rule) { + this.name = name.toLowerCase(); + this.rooms = rule.rooms || []; + this.users = rule.users || []; + this.process = rule.process; + } + + execute(args, room, user) { + return this.process(args, room, user); + } +} + +module.exports.Rule = Rule; diff --git a/showdown/src/tournaments.js b/showdown/src/tournaments.js new file mode 100644 index 0000000..af8c191 --- /dev/null +++ b/showdown/src/tournaments.js @@ -0,0 +1,48 @@ +/** + * Tournaments + * Cassius - https://github.com/sirDonovan/Cassius + * + * This file contains the tournaments manager + * + * @license MIT license + */ + +"use strict"; + +const Tournament = require("./room-tournament").Tournament; + +class Tournaments { + constructor() { + /**@type {{[k: string]: NodeJS.Timer}} */ + this.tournamentTimers = {}; + this.defaultCap = psConfig.defaultTournamentCap || 64; + this.maxCap = 128; + } + + /** + * @param {Room} room + * @param {Format} format + * @param {string} generator + * @return {Tournament} + */ + createTournament(room, format, generator) { + return new Tournament(room, format, generator); + } + + /** + * @param {Room} room + * @param {number} time + * @param {string} [formatid] + * @param {number} [cap] + */ + setTournamentTimer(room, time, formatid, cap) { + if (room.id in this.tournamentTimers) clearTimeout(this.tournamentTimers[room.id]); + if (!cap) cap = this.defaultCap; + this.tournamentTimers[room.id] = setTimeout(() => { + room.say(`/tour new ${formatid}, elimination, ${cap}`); + delete this.tournamentTimers[room.id]; + }, time); + } +} + +module.exports = new Tournaments(); diff --git a/showdown/src/users.js b/showdown/src/users.js new file mode 100644 index 0000000..0adbf07 --- /dev/null +++ b/showdown/src/users.js @@ -0,0 +1,102 @@ +"use strict"; + +const groups = require("./groups.json"); + +const PRUNE_INTERVAL = 60 * 60 * 1000; + +class User { + constructor(name, id) { + this.name = Tools.toName(name); + this.id = id; + this.alts = []; + this.ranks = {}; + this.globalRank = " "; + this.rooms = new Map(); + this.roomsData = new Map(); + this.game = null; + } + + hasRoomRank(room, targetRank) { + let rank; + if (typeof room === "string") { + rank = room; + } else { + rank = this.rooms.get(room); + } + if (!rank) return false; + return groups[rank] >= groups[targetRank]; + } + + hasGlobalRank(targetRank) { + return (groups[this.globalRank] >= groups[targetRank]); + } + + hasAnyRank(targetRank) { + const user = this; + if (groups[user.globalRank] >= groups[targetRank]) { + return true; + } + let returnTrue = false; + Object.keys(user.ranks).forEach(function (key) { + if (groups[user.ranks[key]] >= groups[targetRank]) { + returnTrue = true; + } + }); + return returnTrue; + } + + isDeveloper() { + return psConfig.developers && psConfig.developers.includes(this.id); + } + + say(message) { + message = Tools.normalizeMessage(message); + if (!message) return; + psClient.send(`|/pm ${this.id}, ${message}`); + } +} + +exports.User = User; + +class Users { + constructor() { + this.users = {}; + this.self = this.add(psConfig.username); + this.pruneUsersInterval = setInterval(() => this.pruneUsers(), PRUNE_INTERVAL); + + this.User = User; + } + get(name) { + if (name instanceof User) return name; + return this.users[Tools.toId(name)]; + } + + add(name) { + const id = Tools.toId(name); + let user = this.get(id); + if (!user) { + user = new User(name, id); + this.users[id] = user; + } + return user; + } + + pruneUsers() { + const users = Object.keys(this.users); + users.splice(users.indexOf(this.self.id), 1); + for (let i = 0, len = users.length; i < len; i++) { + const user = this.users[users[i]]; + if (!user.rooms.size) { + delete this.users[user.id]; + } + } + } + + destroyUsers() { + for (const i in this.users) { + delete this.users[i]; + } + } +} + +exports.Users = new Users(); diff --git a/showdown/src/utilities.js b/showdown/src/utilities.js new file mode 100644 index 0000000..1ea9d00 --- /dev/null +++ b/showdown/src/utilities.js @@ -0,0 +1,229 @@ +/*******************************************************************/ +/* */ +/* Discord-specific functions that are useful in multiple commands */ +/* */ +/*******************************************************************/ + +"use strict"; + +const path = require("path"); + +class Utilities { + checkPermissions(message, cmd) { + if (message.channel.type === "dm") return undefined; + this.checkForDb(message.guild.id, `{"name":"${message.guild.name}", "config":{}}`); + const db = Storage.getDatabase(message.guild.id); + db.name = message.guild.name; // Update identifier in case the server has changed name since the last command + if (!(db.config.nsfw)) db.config.nsfw = {"allowNSFW": false, "nsfwChannels": []}; + if (!(db.config.requiredRoles)) db.config.requiredRoles = []; + if (!(db.config.bannedChannels)) db.config.bannedChannels = []; + if (!(db.config.bannedUsers)) db.config.bannedUsers = []; + if (!(db.config.botRanks)) db.config.botRanks = {"manager": [], "elevated": []}; + if (!(db.config.commands)) db.config.commands = {}; + if (!(db.config.commands[cmd])) db.config.commands[cmd] = {"uses": {"total": 0, "users": {}}, "requiredRoles": [], "bannedUsers": [], "bannedChannels": [], "isElevated": false, "isManager": false}; + if (!(db.config.commands[cmd].uses.users[message.author.id])) db.config.commands[cmd].uses.users[message.author.id] = {}; + + db.config.commands[cmd].uses.users[message.author.id].name = `${message.author.username}#${message.author.discriminator}`; // Update identifier in case Username has changed since last time + if (!(db.config.commands[cmd].uses.users[message.author.id].times)) db.config.commands[cmd].uses.users[message.author.id].times = 0; + db.config.commands[cmd].uses.total += 1; + db.config.commands[cmd].uses.users[message.author.id].times += 1; + + Storage.exportDatabase(message.guild.id); + return db; + } + + buildDb(id, name) { + console.log(`${discordText}Building database for ${name.green}...`); + const db = Storage.getDatabase(id); + db.name = name; // Update identifier in case the server has changed name since the last command + if (!(db.config.nsfw)) db.config.nsfw = {"allowNSFW": false, "nsfwChannels": []}; + if (!(db.config.requiredRoles)) db.config.requiredRoles = []; + if (!(db.config.bannedChannels)) db.config.bannedChannels = []; + if (!(db.config.bannedUsers)) db.config.bannedUsers = []; + if (!(db.config.botRanks)) db.config.botRanks = {"manager": [], "elevated": []}; + Storage.exportDatabase(id); + } + + populateDb(id, cmd, type) { + const db = Storage.getDatabase(id); + if (!(db.config.commands)) db.config.commands = {}; + if (!(db.config.commands[cmd])) { + console.log(`${discordText}Adding ${type !== "NSFW" ? cmd.green : cmd.charAt(0).green + "*****".green} to ${(client.guilds.cache.get(id).name).cyan} database...`); + db.config.commands[cmd] = {"uses": {"total": 0, "users": {}}, "requiredRoles": [], "bannedUsers": [], "bannedChannels": [], "isElevated": false, "isManager": false}; + } + Storage.exportDatabase(id); + } + + generateRandomLinkCode(len) { + const length = len > 0 ? len : 4; + if (!(fs.existsSync(path.resolve(__dirname, "../databases/linkCodes.json")))) { + fs.writeFileSync(path.resolve(__dirname, "../databases/linkCodes.json"), `{"linkCodes":[]}`); + Storage.importDatabase("linkCodes"); + } + const lastCodes = Storage.getDatabase("linkCodes"); + let arr = []; + do { + arr = []; + for (let i = 0; i < length; i++) { + arr[i] = Tools.random(10); // 0-9 + } + } while (lastCodes["linkCodes"].includes(arr.join(""))); + const thisCode = arr.join(""); + if (lastCodes["linkCodes"].length === 10) { + lastCodes["linkCodes"].shift(); + } + lastCodes["linkCodes"].push(thisCode); + Storage.exportDatabase("linkCodes"); + return thisCode; + } + + parseUserId(input) { + if (input.includes("<")) { + input = input.match(/^<@!?(\d+)>$/)[1]; + } + return client.users.cache.get(input); + } + + parseRoleId(message, input) { + if (input.includes("<")) { + return message.guild.roles.get(input.match(/^<@&?(\d+)>$/)[1]); + } else { + let name = message.guild.roles.cache.find(r => Tools.toId(r.name) === Tools.toId(input)); + if (!name) name = message.guild.roles.cache.get(input); + return name; + } + } + + parseChannelId(message, input) { + if (input.includes("<")) { + return message.guild.channels.cache.get(input.match(/^<#?(\d+)>$/)[1]); + } else { + let name = message.guild.channels.cache.find(c => Tools.toId(c.name) === Tools.toId(input)); + if (!name) name = message.guild.channels.cache.get(input); + return name; + } + } + + oneIn(number) { + const rand = Tools.random(number); + if (rand === 0) return true; + return false; + } + + toSmogonString(dex) { + let genStr = "ss"; + switch (dex) { + case 8: + genStr = "ss"; + break; + case 7: + genStr = "sm"; + break; + case 6: + genStr = "xy"; + break; + case 5: + genStr = "bw"; + break; + case 4: + genStr = "dp"; + break; + case 3: + genStr = "rs"; + break; + case 2: + genStr = "gs"; + break; + case 1: + genStr = "rb"; + break; + } + return genStr; + } + + dexColorToDec(str) { + let int = 0; + const names = ["red", "blue", "yellow", "green", "black", "brown", "purple", "gray", "white", "pink"]; + const numbers = [15751272, 3180784, 15781960, 4241512, 361861, 11563056, 11036864, 10526880, 15790320, 16289992]; + int = numbers[names.indexOf(str)]; + return int; + } + + checkForDb(name, json, dummyId) { + if (json) { + try { + JSON.parse(json); + } catch (e) { + throw new SyntaxError(`"${json}" is not valid JSON`); + } + } + if (!(fs.existsSync(path.resolve(__dirname, `../../databases/${name}.json`)))) { + fs.writeFileSync(path.resolve(__dirname, `../../databases/${name}.json`), json); + if (dummyId) fs.writeFileSync(path.resolve(__dirname, `../../databases/${name} - ${dummyId}`), "This is a guide file to assist with navigation of the `databases` directory.\nIt can be deleted without consequence if need be."); + } + Storage.importDatabase(name); + } + + getFcCategory(str) { + let category; + switch (Tools.toId(str)) { + case "n3ds": + case "nintendo3ds": + case "2ds": + case "o2ds": + case "o3ds": + case "3ds": + category = "Nintendo 3DS"; + break; + + case "nintendoswitch": + case "nswitch": + case "switchlite": + case "switch": + case "ns": + category = "Nintendo Switch"; + break; + + case "home": + case "pokemonhome": + case "pokémonhome": + category = "Pokémon Home"; + break; + + case "go": + case "pokemongo": + case "pokémongo": + case "pogo": + category = "Pokémon Go"; + break; + + case "mkt": + case "mariokarttour": + category = "Mario Kart Tour"; + break; + + default: + category = Tools.toTitleCase(str); + } + return category; + } + + formatFc(code, cat) { + code = code.toUpperCase(); + if (code.length < 6) return code; + if (code.startsWith("SW") && cat === "Nintendo Switch") { + code = code.slice(2); + } + const splitNumber = Math.floor(code.length / 3); + code = code.split("").reduce((a, e, i) => a + e + (i % splitNumber === splitNumber - 1 ? "-" : ""), ""); + if (code.charAt(code.length - 1) === "-") code = code.substr(0, code.length - 1); + return code; + } + + exportDbAndSend(message, text, db) { + Storage.exportDatabase(db); + return message.channel.send(text); + } +} + +module.exports = new Utilities(); diff --git a/sources/homoglyph.js b/sources/homoglyph.js new file mode 100644 index 0000000..5e671e7 --- /dev/null +++ b/sources/homoglyph.js @@ -0,0 +1,139 @@ +/*******************************************************/ +/* Adapted from codebox/homoglyph https://git.io/JJPMJ */ +/* MIT License */ +/*******************************************************/ +"use strict"; + +const removeDiacritics = require("diacritics").remove; + +const HOMOGLYPHS = { + "-": ["\u{2cba}", "\u{fe58}", "\u{2043}", "\u{2212}", "\u{2011}", "\u{2012}", "\u{2796}", "\u{2013}", "\u{02d7}", "\u{06d4}", "\u{2010}"], + ".": ["\u{a4f8}", "\u{06f0}", "\u{2024}", "\u{ff0e}", "\u{0702}", "\u{1d16d}", "\u{10a50}", "\u{0701}", "\u{a60e}", "\u{0660}"], + "0": ["\u{feeb}", "\u{1d6b6}", "\u{2c9f}", "\u{0c82}", "\u{118d7}", "\u{1d490}", "\u{1d7f6}", "\u{fba6}", "\u{10404}", "\u{1d7d8}", "\u{a4f3}", "\u{0555}", "\u{1d6d0}", "\u{1d77e}", "\u{fbab}", "\u{10ff}", "\u{1d560}", "\u{1d698}", "\u{1d4de}", "\u{0665}", "\u{1d52c}", "\u{0966}", "\u{041e}", "\u{1d40e}", "\u{104c2}", "\u{ff2f}", "\u{1d7bc}", "\u{1d748}", "\u{1d7ce}", "\u{1d4f8}", "\u{1d764}", "\u{0b66}", "\u{1d442}", "\u{1d630}", "\u{0585}", "\u{1d5ae}", "\u{1042c}", "\u{0647}", "\u{1d594}", "\u{0d20}", "\u{118b5}", "\u{1d782}", "\u{104ea}", "\u{0ed0}", "\u{0c66}", "\u{1d6f0}", "\u{3007}", "\u{09e6}", "\u{1d70a}", "\u{1d11}", "\u{1d428}", "\u{0d82}", "\u{1d476}", "\u{1d7b8}", "\u{114d0}", "\u{0d02}", "\u{1d5fc}", "\u{fba7}", "\u{0b20}", "\u{06d5}", "\u{1d45c}", "\u{fbaa}", "\u{10292}", "\u{1d546}", "\u{1d5e2}", "\u{1d67e}", "\u{1d72a}", "\u{ab3d}", "\u{1ee24}", "\u{06be}", "\u{03bf}", "\u{0d66}", "\u{feea}", "\u{10516}", "\u{118c8}", "\u{2134}", "\u{1ee64}", "\u{1d70e}", "\u{ff4f}", "\u{06f5}", "\u{1d616}", "\u{1d0f}", "\u{043e}", "\u{1d57a}", "\u{ff10}", "\u{1d7e2}", "\u{06c1}", "\u{1d4aa}", "\u{0ce6}", "\u{2c9e}", "\u{118e0}", "\u{2d54}", "\u{1040}", "\u{1d512}", "\u{fbac}", "\u{0be6}", "\u{0c02}", "\u{1d744}", "\u{101d}", "\u{1d664}", "\u{0ae6}", "\u{006f}", "\u{039f}", "\u{fbad}", "\u{fba9}", "\u{0a66}", "\u{03c3}", "\u{12d0}", "\u{1d5c8}", "\u{05e1}", "\u{fba8}", "\u{fee9}", "\u{1d79e}", "\u{feec}", "\u{1d7ec}", "\u{07c0}", "\u{1d6d4}", "\u{1d64a}", "\u{0e50}", "\u{1ee84}", "\u{1fbf0}", "\u{102ab}", "\u{004f}"], + "1": ["\u{0406}", "\u{ff4c}", "\u{23fd}", "\u{05c0}", "\u{217c}", "\u{1d6b0}", "\u{05df}", "\u{1d7f7}", "\u{1d43c}", "\u{1d4d8}", "\u{1d591}", "\u{1d661}", "\u{01c0}", "\u{fe8d}", "\u{1d5a8}", "\u{2110}", "\u{1d529}", "\u{10320}", "\u{1d7ed}", "\u{a4f2}", "\u{1d425}", "\u{1d6ea}", "\u{1d610}", "\u{07ca}", "\u{1d724}", "\u{1d4c1}", "\u{1d798}", "\u{0627}", "\u{2d4f}", "\u{2160}", "\u{1d7cf}", "\u{1d62d}", "\u{1d5f9}", "\u{ffe8}", "\u{1d4f5}", "\u{16f28}", "\u{1d459}", "\u{2111}", "\u{1d48d}", "\u{2223}", "\u{10309}", "\u{04c0}", "\u{0049}", "\u{1ee00}", "\u{0196}", "\u{16c1}", "\u{1d540}", "\u{1d5dc}", "\u{1d55d}", "\u{1ee80}", "\u{ff29}", "\u{2113}", "\u{2c92}", "\u{1d7e3}", "\u{1d678}", "\u{1d574}", "\u{1d695}", "\u{0399}", "\u{1d644}", "\u{1028a}", "\u{1d7d9}", "\u{fe8e}", "\u{ff11}", "\u{1d75e}", "\u{06f1}", "\u{007c}", "\u{1e8c7}", "\u{05d5}", "\u{006c}", "\u{1d470}", "\u{1fbf1}", "\u{0661}", "\u{1d408}", "\u{1d5c5}"], + "2": ["\u{a644}", "\u{14bf}", "\u{1d7f8}", "\u{ff12}", "\u{03e8}", "\u{a6ef}", "\u{1fbf2}", "\u{01a7}", "\u{1d7ee}", "\u{1d7d0}", "\u{1d7e4}", "\u{1d7da}", "\u{a75a}"], + "3": ["\u{1d7f9}", "\u{04e0}", "\u{1fbf3}", "\u{1d206}", "\u{021c}", "\u{1d7e5}", "\u{0417}", "\u{118ca}", "\u{01b7}", "\u{1d7ef}", "\u{1d7d1}", "\u{ff13}", "\u{a76a}", "\u{a7ab}", "\u{2ccc}", "\u{16f3b}", "\u{1d7db}"], + "4": ["\u{1d7f0}", "\u{1d7fa}", "\u{ff14}", "\u{1d7d2}", "\u{1d7e6}", "\u{118af}", "\u{1fbf4}", "\u{1d7dc}", "\u{13ce}"], + "5": ["\u{1fbf5}", "\u{1d7e7}", "\u{1d7d3}", "\u{118bb}", "\u{1d7fb}", "\u{ff15}", "\u{01bc}", "\u{1d7dd}", "\u{1d7f1}"], + "6": ["\u{2cd2}", "\u{0431}", "\u{1d7e8}", "\u{1fbf6}", "\u{1d7de}", "\u{13ee}", "\u{118d5}", "\u{1d7fc}", "\u{ff16}", "\u{1d7f2}", "\u{1d7d4}"], + "7": ["\u{118c6}", "\u{1d7fd}", "\u{1d7f3}", "\u{1d7df}", "\u{104d2}", "\u{1d212}", "\u{1d7d5}", "\u{1fbf7}", "\u{ff17}", "\u{1d7e9}"], + "8": ["\u{0223}", "\u{1fbf8}", "\u{0a6a}", "\u{1031a}", "\u{0222}", "\u{1d7fe}", "\u{1d7d6}", "\u{1d7e0}", "\u{ff18}", "\u{1d7ea}", "\u{1d7f4}", "\u{0b03}", "\u{09ea}", "\u{1e8cb}"], + "9": ["\u{1d7f5}", "\u{a76e}", "\u{09ed}", "\u{1d7e1}", "\u{0b68}", "\u{ff19}", "\u{1d7ff}", "\u{118d6}", "\u{1fbf9}", "\u{2cca}", "\u{0a67}", "\u{1d7d7}", "\u{118cc}", "\u{0d6d}", "\u{118ac}", "\u{1d7eb}"], + "A": ["\u{16f40}", "\u{1d71c}", "\u{1d6a8}", "\u{1d504}", "\u{1d756}", "\u{0410}", "\u{1d6e2}", "\u{1d49c}", "\u{a4ee}", "\u{1d790}", "\u{15c5}", "\u{ff21}", "\u{1d00}", "\u{0391}", "\u{1d468}", "\u{1d538}", "\u{1d5d4}", "\u{1d63c}", "\u{102a0}", "\u{1d56c}", "\u{1d5a0}", "\u{1d434}", "\u{1d670}", "\u{13aa}", "\u{1d400}", "\u{1d608}", "\u{1d4d0}", "\u{ab7a}", "\u{0040}", "\u{15E9}", "\u{1f170}", "\u{5342}", "\u{039B}", "\u{FF91}", "\u{20B3}", "\u{0E04}", "\u{13D7}", "\u{1D2C}", "\u{1F130}", "\u{2200}", "\u{AA96}", "\u{0394}", "\u{2206}", "\u{1403}", "\u{10300}", "\u{2D60}", "\u{1D759}", "\u{1431}", "\u{2D37}", "\u{07E1}", "\u{1D760}", "\u{2227}", "\u{10321}", "\u{0466}", "\u{1D6B2}", "\u{212B}", "\u{22C0}", "\u{A554}", "\u{1D793}", "\u{1D6AB}", "\u{15CB}", "\u{A658}", "\u{1D79A}", "\u{1D71F}", "\u{0668}", "\u{06F8}", "\u{1D27}", "\u{1D6EC}", "\u{1D726}", "\u{0467}", "\u{A659}", "\u{1F702}", "\u{2A5C}", "\u{22CF}", "\u{29CD}", "\u{2C86}", "\u{0386}", "\u{1F1E6}", "\u{FE0F}", "\u{1F08}"], + "B": ["\u{1d505}", "\u{0412}", "\u{13fc}", "\u{102a1}", "\u{1d63d}", "\u{1d5d5}", "\u{212c}", "\u{1d5a1}", "\u{1d469}", "\u{16d2}", "\u{1d6e3}", "\u{a4d0}", "\u{a7b4}", "\u{1d6a9}", "\u{15f7}", "\u{1d4d1}", "\u{1d791}", "\u{10282}", "\u{1d757}", "\u{13f4}", "\u{10301}", "\u{1d71d}", "\u{1d401}", "\u{0392}", "\u{1d609}", "\u{0299}", "\u{0432}", "\u{1d435}", "\u{1d56d}", "\u{ff22}", "\u{1d671}", "\u{1d539}"], + "C": ["\u{0421}", "\u{118f2}", "\u{1d402}", "\u{03f9}", "\u{a4da}", "\u{10415}", "\u{ff23}", "\u{1d4d2}", "\u{1d436}", "\u{1d56e}", "\u{1d60a}", "\u{10302}", "\u{2ca4}", "\u{216d}", "\u{1d672}", "\u{1d5d6}", "\u{1051c}", "\u{102a2}", "\u{1d46a}", "\u{1d5a2}", "\u{13df}", "\u{118e9}", "\u{1f74c}", "\u{1d63e}", "\u{1d49e}", "\u{2102}", "\u{212d}"], + "D": ["\u{1d673}", "\u{13a0}", "\u{1d403}", "\u{1d437}", "\u{ab70}", "\u{216e}", "\u{1d49f}", "\u{1d507}", "\u{1d56f}", "\u{2145}", "\u{15ea}", "\u{1d5a3}", "\u{1d63f}", "\u{1d4d3}", "\u{15de}", "\u{1d5d7}", "\u{1d46b}", "\u{a4d3}", "\u{1d05}", "\u{ff24}", "\u{1d53b}", "\u{1d60b}", "\u{53E5}", "\u{0E54}"], + "E": ["\u{1d60c}", "\u{1d404}", "\u{1d6ac}", "\u{1d4d4}", "\u{1d438}", "\u{10286}", "\u{1d6e6}", "\u{1d508}", "\u{22ff}", "\u{118ae}", "\u{1d5a4}", "\u{0415}", "\u{1d46c}", "\u{118a6}", "\u{2d39}", "\u{1d53c}", "\u{1d570}", "\u{0395}", "\u{ab7c}", "\u{a4f0}", "\u{2130}", "\u{1d5d8}", "\u{1d75a}", "\u{1d640}", "\u{1d674}", "\u{1d720}", "\u{13ac}", "\u{1d07}", "\u{1d794}", "\u{ff25}", "\u{0454}", "\u{15F4}", "\u{2d5f}", "\u{1d31}", "\u{a5cb}", "\u{2107}", "\u{a72a}", "\u{0404}", "\u{03b5}", "\u{03f5}", "\u{a72b}", "\u{0510}", "\u{1d774}", "\u{df74}", "\u{1d7ae}", "\u{dfae}", "\u{1d78a}", "\u{df8a}", "\u{1d7c4}", "\u{dfc4}", "\u{1d700}", "\u{df00}", "\u{1d73a}", "\u{df3a}", "\u{1d6c6}", "\u{dec6}", "\u{2208}", "\u{1d750}", "\u{df50}", "\u{0511}", "\u{2203}", "\u{2d47}", "\u{1d6dc}", "\u{dedc}", "\u{13cb}", "\u{1d7a2}", "\u{dfa2}", "\u{1d77d}", "\u{0437}", "\u{30E8}"], + "F": ["\u{1d509}", "\u{10287}", "\u{118a2}", "\u{1d571}", "\u{15b4}", "\u{1d439}", "\u{1d46d}", "\u{1d641}", "\u{2131}", "\u{118c2}", "\u{ff26}", "\u{1d53d}", "\u{1d213}", "\u{1d5a5}", "\u{03dc}", "\u{1d7ca}", "\u{1d60d}", "\u{1d405}", "\u{1d5d9}", "\u{a4dd}", "\u{1d675}", "\u{a798}", "\u{10525}", "\u{102a5}", "\u{1d4d5}"], + "G": ["\u{1d53e}", "\u{1d46e}", "\u{13c0}", "\u{1d572}", "\u{1d406}", "\u{1d5a6}", "\u{1d642}", "\u{1d5da}", "\u{13fb}", "\u{1d4d6}", "\u{1d43a}", "\u{0262}", "\u{1d4a2}", "\u{1d50a}", "\u{1d676}", "\u{ab90}", "\u{050c}", "\u{13f3}", "\u{050d}", "\u{a4d6}", "\u{ff27}", "\u{1d60e}"], + "H": ["\u{102cf}", "\u{1d6e8}", "\u{ff28}", "\u{1d60f}", "\u{210d}", "\u{13bb}", "\u{2c8e}", "\u{1d722}", "\u{a4e7}", "\u{0397}", "\u{1d75c}", "\u{157c}", "\u{210b}", "\u{1d5db}", "\u{1d796}", "\u{1d573}", "\u{1d6ae}", "\u{029c}", "\u{1d407}", "\u{041d}", "\u{ab8b}", "\u{210c}", "\u{1d677}", "\u{1d5a7}", "\u{1d643}", "\u{043d}", "\u{1d4d7}", "\u{1d46f}", "\u{1d43b}"], + "I": ["\u{0406}", "\u{ff4c}", "\u{23fd}", "\u{05c0}", "\u{217c}", "\u{1d6b0}", "\u{05df}", "\u{1d7f7}", "\u{1d43c}", "\u{1d4d8}", "\u{1d591}", "\u{1d661}", "\u{01c0}", "\u{fe8d}", "\u{1d5a8}", "\u{2110}", "\u{1d529}", "\u{10320}", "\u{1d7ed}", "\u{a4f2}", "\u{1d425}", "\u{1d6ea}", "\u{1d610}", "\u{07ca}", "\u{1d724}", "\u{1d4c1}", "\u{1d798}", "\u{0627}", "\u{2d4f}", "\u{2160}", "\u{1d7cf}", "\u{1d62d}", "\u{1d5f9}", "\u{ffe8}", "\u{0031}", "\u{1d4f5}", "\u{16f28}", "\u{1d459}", "\u{2111}", "\u{1d48d}", "\u{2223}", "\u{10309}", "\u{04c0}", "\u{1ee00}", "\u{0196}", "\u{16c1}", "\u{1d540}", "\u{1d5dc}", "\u{1d55d}", "\u{1ee80}", "\u{ff29}", "\u{2113}", "\u{2c92}", "\u{1d7e3}", "\u{1d678}", "\u{1d574}", "\u{1d695}", "\u{0399}", "\u{1d644}", "\u{1028a}", "\u{1d7d9}", "\u{fe8e}", "\u{ff11}", "\u{1d75e}", "\u{06f1}", "\u{007c}", "\u{1e8c7}", "\u{05d5}", "\u{006c}", "\u{1d470}", "\u{1fbf1}", "\u{0661}", "\u{1d408}", "\u{1d5c5}"], + "J": ["\u{a4d9}", "\u{a7b2}", "\u{1d5a9}", "\u{1d4a5}", "\u{1d645}", "\u{1d5dd}", "\u{1d0a}", "\u{ab7b}", "\u{13ab}", "\u{ff2a}", "\u{1d409}", "\u{1d541}", "\u{037f}", "\u{1d471}", "\u{1d50d}", "\u{1d575}", "\u{148d}", "\u{1d4d9}", "\u{0408}", "\u{1d43d}", "\u{1d611}", "\u{1d679}"], + "K": ["\u{1d50e}", "\u{1d75f}", "\u{039a}", "\u{1d6eb}", "\u{1d799}", "\u{1d4a6}", "\u{2c94}", "\u{041a}", "\u{1d612}", "\u{1d576}", "\u{1d542}", "\u{ff2b}", "\u{1d43e}", "\u{a4d7}", "\u{1d67a}", "\u{16d5}", "\u{1d725}", "\u{10518}", "\u{1d472}", "\u{1d4da}", "\u{1d5aa}", "\u{1d40a}", "\u{1d5de}", "\u{1d646}", "\u{1d6b1}", "\u{212a}", "\u{13e6}"], + "L": ["\u{029f}", "\u{118a3}", "\u{1d22a}", "\u{abae}", "\u{1d647}", "\u{1041b}", "\u{1d473}", "\u{1d43f}", "\u{2cd0}", "\u{216c}", "\u{14aa}", "\u{1d4db}", "\u{10526}", "\u{1d50f}", "\u{ff2c}", "\u{10443}", "\u{1d577}", "\u{1d5df}", "\u{1d613}", "\u{1d543}", "\u{1d67b}", "\u{118b2}", "\u{1d5ab}", "\u{a4e1}", "\u{1d40b}", "\u{2112}", "\u{2cd1}", "\u{16f16}", "\u{13de}"], + "M": ["\u{10311}", "\u{1d5ac}", "\u{1d67c}", "\u{16d6}", "\u{1d4dc}", "\u{1d474}", "\u{1d510}", "\u{2c98}", "\u{041c}", "\u{15f0}", "\u{03fa}", "\u{039c}", "\u{1d5e0}", "\u{1d578}", "\u{1d440}", "\u{ff2d}", "\u{216f}", "\u{1d6b3}", "\u{1d79b}", "\u{1d40c}", "\u{1d727}", "\u{1d544}", "\u{1d614}", "\u{1d761}", "\u{102b0}", "\u{2133}", "\u{1d6ed}", "\u{13b7}", "\u{1d648}", "\u{a4df}"], + "N": ["\u{1d5ad}", "\u{1d40d}", "\u{ff2e}", "\u{1d6ee}", "\u{2115}", "\u{1d4dd}", "\u{1d649}", "\u{a4e0}", "\u{1d475}", "\u{1d441}", "\u{1d579}", "\u{0274}", "\u{2c9a}", "\u{1d79c}", "\u{1d615}", "\u{1d67d}", "\u{1d728}", "\u{1d5e1}", "\u{1d511}", "\u{039d}", "\u{1d4a9}", "\u{1d762}", "\u{10513}", "\u{1d6b4}", "\u{51E0}", "\u{1DB0}"], + "O": ["\u{feeb}", "\u{1d6b6}", "\u{2c9f}", "\u{0c82}", "\u{0030}", "\u{118d7}", "\u{1d490}", "\u{1d7f6}", "\u{fba6}", "\u{10404}", "\u{1d7d8}", "\u{a4f3}", "\u{0555}", "\u{1d6d0}", "\u{1d77e}", "\u{fbab}", "\u{10ff}", "\u{1d560}", "\u{1d698}", "\u{1d4de}", "\u{0665}", "\u{1d52c}", "\u{0966}", "\u{041e}", "\u{1d40e}", "\u{104c2}", "\u{ff2f}", "\u{1d7bc}", "\u{1d748}", "\u{1d7ce}", "\u{1d4f8}", "\u{1d764}", "\u{0b66}", "\u{1d442}", "\u{1d630}", "\u{0585}", "\u{1d5ae}", "\u{1042c}", "\u{0647}", "\u{1d594}", "\u{0d20}", "\u{118b5}", "\u{1d782}", "\u{104ea}", "\u{0ed0}", "\u{0c66}", "\u{1d6f0}", "\u{3007}", "\u{09e6}", "\u{1d70a}", "\u{1d11}", "\u{1d428}", "\u{0d82}", "\u{1d476}", "\u{1d7b8}", "\u{114d0}", "\u{0d02}", "\u{1d5fc}", "\u{fba7}", "\u{0b20}", "\u{06d5}", "\u{1d45c}", "\u{fbaa}", "\u{10292}", "\u{1d546}", "\u{1d5e2}", "\u{1d67e}", "\u{1d72a}", "\u{ab3d}", "\u{1ee24}", "\u{06be}", "\u{03bf}", "\u{0d66}", "\u{feea}", "\u{10516}", "\u{118c8}", "\u{2134}", "\u{1ee64}", "\u{1d70e}", "\u{ff4f}", "\u{06f5}", "\u{1d616}", "\u{1d0f}", "\u{043e}", "\u{1d57a}", "\u{ff10}", "\u{1d7e2}", "\u{06c1}", "\u{1d4aa}", "\u{0ce6}", "\u{2c9e}", "\u{118e0}", "\u{2d54}", "\u{1040}", "\u{1d512}", "\u{fbac}", "\u{0be6}", "\u{0c02}", "\u{1d744}", "\u{101d}", "\u{1d664}", "\u{0ae6}", "\u{006f}", "\u{039f}", "\u{fbad}", "\u{fba9}", "\u{0a66}", "\u{03c3}", "\u{12d0}", "\u{1d5c8}", "\u{05e1}", "\u{fba8}", "\u{fee9}", "\u{1d79e}", "\u{feec}", "\u{1d7ec}", "\u{07c0}", "\u{1d6d4}", "\u{1d64a}", "\u{0e50}", "\u{1ee84}", "\u{1fbf0}", "\u{102ab}", "\u{15DD}", "\u{26aa}", "\u{10486}", "\u{dc86}", "\u{2b2f}", "\u{1d6f3}", "\u{def3}", "\u{04e8}", "\u{213a}", "\u{1d72d}", "\u{df2d}", "\u{03f4}", "\u{3147}", "\u{1f535}", "\u{dd35}", "\u{2609}", "\u{26ab}", "\u{1f534}", "\u{56DE}", "\u{3116}"], + "P": ["\u{03a1}", "\u{a4d1}", "\u{1d57b}", "\u{1d29}", "\u{1d67f}", "\u{146d}", "\u{1d5e3}", "\u{1d5af}", "\u{1d477}", "\u{1d4df}", "\u{1d4ab}", "\u{0420}", "\u{abb2}", "\u{1d7a0}", "\u{13e2}", "\u{1d18}", "\u{1d40f}", "\u{2ca2}", "\u{2119}", "\u{10295}", "\u{1d617}", "\u{ff30}", "\u{1d64b}", "\u{1d766}", "\u{1d443}", "\u{1d6b8}", "\u{1d6f2}", "\u{1d513}", "\u{1d72c}"], + "Q": ["\u{1d410}", "\u{1d514}", "\u{1d57c}", "\u{1d5e4}", "\u{ff31}", "\u{1d64c}", "\u{1d680}", "\u{1d618}", "\u{1d478}", "\u{1d4ac}", "\u{1d5b0}", "\u{2d55}", "\u{211a}", "\u{1d4e0}", "\u{1d444}"], + "R": ["\u{1d479}", "\u{1d619}", "\u{0280}", "\u{1d57d}", "\u{1d411}", "\u{1d5e5}", "\u{13d2}", "\u{16b1}", "\u{ab71}", "\u{1d64d}", "\u{1d445}", "\u{aba2}", "\u{1587}", "\u{211c}", "\u{1d5b1}", "\u{104b4}", "\u{01a6}", "\u{1d216}", "\u{16f35}", "\u{1d681}", "\u{1d4e1}", "\u{211b}", "\u{a4e3}", "\u{211d}", "\u{13a1}", "\u{ff32}", "\u{044F}", "\u{5C3A}"], + "S": ["\u{1d57e}", "\u{13da}", "\u{1d682}", "\u{13d5}", "\u{10420}", "\u{ff33}", "\u{1d516}", "\u{1d61a}", "\u{054f}", "\u{10296}", "\u{0405}", "\u{1d5b2}", "\u{1d5e6}", "\u{1d47a}", "\u{1d4e2}", "\u{1d446}", "\u{16f3a}", "\u{a4e2}", "\u{1d54a}", "\u{1d412}", "\u{1d64e}", "\u{1d4ae}"], + "T": ["\u{22a4}", "\u{2ca6}", "\u{13a2}", "\u{1d72f}", "\u{03c4}", "\u{1d57f}", "\u{ab72}", "\u{16f0a}", "\u{1d1b}", "\u{0442}", "\u{1d70f}", "\u{1d47b}", "\u{1d683}", "\u{27d9}", "\u{1d54b}", "\u{1d517}", "\u{03a4}", "\u{10315}", "\u{1d4af}", "\u{1d5e7}", "\u{1d7a3}", "\u{1d749}", "\u{102b1}", "\u{1d413}", "\u{1d64f}", "\u{ff34}", "\u{0422}", "\u{1d5b3}", "\u{a4d4}", "\u{1d6f5}", "\u{1d61b}", "\u{1d6bb}", "\u{1d769}", "\u{1d6d5}", "\u{1d7bd}", "\u{118bc}", "\u{1f768}", "\u{1d447}", "\u{10297}", "\u{1d783}", "\u{1d4e3}"], + "U": ["\u{054d}", "\u{222a}", "\u{118b8}", "\u{1d47c}", "\u{1d580}", "\u{a4f4}", "\u{1d414}", "\u{1d448}", "\u{1d684}", "\u{16f42}", "\u{1d518}", "\u{144c}", "\u{1d54c}", "\u{1d650}", "\u{1d4e4}", "\u{1200}", "\u{1d5e8}", "\u{1d61c}", "\u{1d5b4}", "\u{1d4b0}", "\u{22c3}", "\u{104ce}", "\u{ff35}"], + "V": ["\u{1d581}", "\u{1d47d}", "\u{1d20d}", "\u{2d38}", "\u{a4e6}", "\u{1d5e9}", "\u{1d519}", "\u{1d4b1}", "\u{142f}", "\u{1d685}", "\u{1051d}", "\u{1d61d}", "\u{16f08}", "\u{0474}", "\u{13d9}", "\u{ff36}", "\u{1d415}", "\u{2164}", "\u{0667}", "\u{1d54d}", "\u{1d651}", "\u{118a0}", "\u{1d4e5}", "\u{a6df}", "\u{06f7}", "\u{1d5b5}", "\u{1d449}"], + "W": ["\u{118ef}", "\u{1d686}", "\u{1d416}", "\u{1d4e6}", "\u{13b3}", "\u{1d47e}", "\u{1d5b6}", "\u{1d582}", "\u{1d44a}", "\u{1d4b2}", "\u{13d4}", "\u{1d61e}", "\u{1d5ea}", "\u{a4ea}", "\u{118e6}", "\u{ff37}", "\u{1d652}", "\u{051c}", "\u{1d51a}", "\u{1d54e}"], + "X": ["\u{1d76c}", "\u{1d54f}", "\u{1d5eb}", "\u{1d4b3}", "\u{166d}", "\u{102b4}", "\u{1d44b}", "\u{2573}", "\u{1d47f}", "\u{10290}", "\u{ff38}", "\u{1d583}", "\u{1d7a6}", "\u{2169}", "\u{1d61f}", "\u{10527}", "\u{1d417}", "\u{1d732}", "\u{1d6f8}", "\u{1d5b7}", "\u{2d5d}", "\u{1d653}", "\u{1d687}", "\u{10322}", "\u{2cac}", "\u{1d4e7}", "\u{1d51b}", "\u{1d6be}", "\u{a4eb}", "\u{0425}", "\u{16b7}", "\u{03a7}", "\u{10317}", "\u{a7b3}", "\u{118ec}"], + "Y": ["\u{1d418}", "\u{1d480}", "\u{1d654}", "\u{a4ec}", "\u{1d44c}", "\u{1d7a4}", "\u{13bd}", "\u{1d5ec}", "\u{1d4b4}", "\u{1d620}", "\u{1d6f6}", "\u{1d5b8}", "\u{1d4e8}", "\u{0423}", "\u{118a4}", "\u{1d76a}", "\u{16f43}", "\u{04ae}", "\u{1d688}", "\u{1d550}", "\u{1d584}", "\u{2ca8}", "\u{03d2}", "\u{1d51c}", "\u{13a9}", "\u{ff39}", "\u{1d6bc}", "\u{1d730}", "\u{03a5}", "\u{102b2}", "\u{3068}"], + "Z": ["\u{a4dc}", "\u{1d6ad}", "\u{1d795}", "\u{102f5}", "\u{1d44d}", "\u{1d4e9}", "\u{1d75b}", "\u{0396}", "\u{1d419}", "\u{1d621}", "\u{118e5}", "\u{1d585}", "\u{1d655}", "\u{118a9}", "\u{13c3}", "\u{2124}", "\u{1d5b9}", "\u{1d721}", "\u{1d481}", "\u{1d4b5}", "\u{1d689}", "\u{1d6e7}", "\u{1d5ed}", "\u{ff3a}", "\u{2128}"], + "a": ["\u{0061}", "\u{0430}", "\u{2090}", "\u{1d5ba}", "\u{ddba}", "\u{1d43}", "\u{1d5ee}", "\u{ddee}", "\u{1d622}", "\u{de22}", "\u{018b}", "\u{1d68a}", "\u{de8a}", "\u{1d656}", "\u{de56}", "\u{10db}", "\u{1d7c3}", "\u{dfc3}", "\u{1d41a}", "\u{dc1a}", "\u{0363}", "\u{2202}", "\u{018c}", "\u{1d789}", "\u{df89}", "\u{0105}", "\u{0251}", "\u{03b1}", "\u{1d74f}", "\u{df4f}", "\u{237a}", "\u{1d770}", "\u{df70}", "\u{1d482}", "\u{dc82}", "\u{1d7aa}", "\u{dfaa}", "\u{07e5}", "\u{1951}", "\u{1972}", "\u{1d44e}", "\u{dc4e}", "\u{10e8}", "\u{1d736}", "\u{1D6FC}", "\u{1D45}", "\u{1D586}", "\u{0EA5}", "\u{1D6C2}", "\u{1D4EA}", "\u{1D715}", "\u{0E25}"], + "b": ["\u{0062}", "\u{10ee}", "\u{0253}", "\u{15af}", "\u{1d5bb}", "\u{ddbb}", "\u{07d5}", "\u{1d623}", "\u{de23}", "\u{042c}", "\u{1472}", "\u{0185}", "\u{1d41b}", "\u{dc1b}", "\u{1d5ef}", "\u{ddef}", "\u{0180}", "\u{1d47}", "\u{0184}", "\u{0183}", "\u{042a}", "\u{048c}", "\u{2422}", "\u{1483}", "\u{13cf}", "\u{1d6c}", "\u{1d657}", "\u{de57}", "\u{044c}", "\u{1d68b}", "\u{de8b}", "\u{10a6}", "\u{03e6}", "\u{2c13}", "\u{1d587}", "\u{dd87}", "\u{1d483}", "\u{dc83}", "\u{1d44f}", "\u{dc4f}", "\u{048d}", "\u{044a}", "\u{266d}", "\u{1473}", "\u{2c43}", "\u{a64e}", "\u{147f}", "\u{1579}", "\u{1d51f}", "\u{dd1f}", "\u{1D553}"], + "c": ["\u{0043}", "\u{03f9}", "\u{1d5a2}", "\u{dda2}", "\u{216d}", "\u{1d5d6}", "\u{ddd6}", "\u{0421}", "\u{0063}", "\u{03f2}", "\u{2201}", "\u{0441}", "\u{1d5bc}", "\u{ddbc}", "\u{1d63e}", "\u{de3e}", "\u{217d}", "\u{1455}", "\u{1d04}", "\u{1d60a}", "\u{de0a}", "\u{1d9c}", "\u{1d624}", "\u{de24}", "\u{13df}", "\u{2ca4}", "\u{1466}", "\u{14bc}", "\u{104a8}", "\u{dca8}", "\u{1d41c}", "\u{dc1c}", "\u{1d5f0}", "\u{ddf0}", "\u{2ca5}", "\u{1d672}", "\u{de72}", "\u{1d658}", "\u{de58}", "\u{13e3}", "\u{0297}", "\u{1d68c}", "\u{de8c}", "\u{2d4e}", "\u{1d484}", "\u{dc84}", "\u{0368}", "\u{1d450}", "\u{dc50}", "\u{00e7}", "\u{1d4d2}", "\u{dcd2}", "\u{1974}", "\u{1d4ec}", "\u{00A2}", "\u{1D554}", "\u{03fe}", "\u{2e26}", "\u{096e}", "\u{037c}", "\u{1462}", "\u{2103}", "\u{1004}", "\u{122d}", "\u{1f1e8}", "\u{dde8}", "\u{531a}"], + "d": ["\u{1d659}", "\u{ff44}", "\u{1d41d}", "\u{13e7}", "\u{a4d2}", "\u{1d4ed}", "\u{1d5bd}", "\u{1d521}", "\u{0501}", "\u{2146}", "\u{1d451}", "\u{1d485}", "\u{1d555}", "\u{1d68d}", "\u{146f}", "\u{1d5f1}", "\u{217e}", "\u{1d589}", "\u{1d625}", "\u{1d4b9}", "\u{10eb}", "\u{056a}", "\u{0500}", "\u{1d48}", "\u{053a}", "\u{0369}", "\u{1d6d}", "\u{1577}", "\u{1d6ff}", "\u{deff}", "\u{0502}", "\u{1470}", "\u{1d6db}"], + "e": ["\u{1d556}", "\u{1d68e}", "\u{1d522}", "\u{04bd}", "\u{2147}", "\u{212e}", "\u{1d65a}", "\u{1d486}", "\u{1d5be}", "\u{1d41e}", "\u{1d626}", "\u{0435}", "\u{ab32}", "\u{1d452}", "\u{1d5f2}", "\u{1d58a}", "\u{212f}", "\u{ff45}", "\u{1d4ee}", "\u{2091}", "\u{1d49}", "\u{04d8}", "\u{0259}", "\u{04bc}", "\u{0258}", "\u{1971}", "\u{04d9}", "\u{156a}", "\u{1566}", "\u{5df3}", "\u{0c32}", "\u{5df2}", "\u{0b67}", "\u{0d32}"], + "f": ["\u{1d65b}", "\u{1d7cb}", "\u{1d5bf}", "\u{1d523}", "\u{1d41f}", "\u{1d487}", "\u{1d627}", "\u{1d557}", "\u{a799}", "\u{1d453}", "\u{0584}", "\u{1d5f3}", "\u{1d58b}", "\u{017f}", "\u{03dd}", "\u{ff46}", "\u{1d4ef}", "\u{1d4bb}", "\u{1e9d}", "\u{1d68f}", "\u{ab35}", "\u{1da0}", "\u{2a0d}", "\u{1e9c}", "\u{2a0e}", "\u{0493}", "\u{1d73}", "\u{1d82}", "\u{2a0f}", "\u{1f761}", "\u{df61}", "\u{0562}"], + "g": ["\u{1d558}", "\u{1d690}", "\u{0261}", "\u{1d58c}", "\u{1d83}", "\u{1d488}", "\u{0581}", "\u{1d454}", "\u{210a}", "\u{1d628}", "\u{1d65c}", "\u{1d5f4}", "\u{1d4f0}", "\u{ff47}", "\u{018d}", "\u{1d524}", "\u{1d5c0}", "\u{1d420}", "\u{1da2}", "\u{1d4d}", "\u{0551}", "\u{10d2}", "\u{100c}"], + "h": ["\u{0570}", "\u{1d421}", "\u{1d629}", "\u{1d559}", "\u{1d525}", "\u{1d5f5}", "\u{210e}", "\u{ff48}", "\u{04bb}", "\u{1d489}", "\u{1d58d}", "\u{1d65d}", "\u{13c2}", "\u{1d4f1}", "\u{1d691}", "\u{1d5c1}", "\u{1d4bd}", "\u{10b9}", "\u{04ba}", "\u{02b0}", "\u{056b}", "\u{0266}", "\u{a727}", "\u{2095}", "\u{144b}", "\u{02b1}", "\u{a695}", "\u{0526}", "\u{0267}", "\u{2644}", "\u{10337}", "\u{df37}", "\u{a694}", "\u{045b}", "\u{036a}", "\u{0452}", "\u{210f}", "\u{0527}", "\u{10ac}", "\u{10e9}", "\u{3093}", "\u{10485}"], + "i": ["\u{2148}", "\u{1d422}", "\u{1d6a4}", "\u{0131}", "\u{1d7b2}", "\u{1d65e}", "\u{1d4f2}", "\u{1d48a}", "\u{ff49}", "\u{0456}", "\u{1d4be}", "\u{2170}", "\u{1d55a}", "\u{1d58e}", "\u{0269}", "\u{04cf}", "\u{1d6ca}", "\u{02db}", "\u{1d5c2}", "\u{037a}", "\u{118c3}", "\u{1d456}", "\u{a647}", "\u{1d778}", "\u{2373}", "\u{1d62a}", "\u{1d73e}", "\u{ab75}", "\u{1fbe}", "\u{13a5}", "\u{026a}", "\u{2139}", "\u{1d526}", "\u{1d692}", "\u{1d5f6}", "\u{1d704}", "\u{03b9}", "\u{1d62}", "\u{a71f}", "\u{a71e}", "\u{fb4b}", "\u{1fd9}", "\u{2071}", "\u{1d09}", "\u{1fd1}", "\u{fe83}", "\u{1f30}", "\u{0623}", "\u{1f31}", "\u{03af}", "\u{fe84}", "\u{1f76}", "\u{1fd8}", "\u{1fd0}", "\u{1f77}", "\u{fe82}", "\u{1f38}", "\u{8ba0}", "\u{0f0f}", "\u{0390}"], + "j": ["\u{1d62b}", "\u{1d55b}", "\u{1d423}", "\u{1d48b}", "\u{1d58f}", "\u{03f3}", "\u{ff4a}", "\u{1d5c3}", "\u{1d4bf}", "\u{1d65f}", "\u{0458}", "\u{1d527}", "\u{1d5f7}", "\u{1d4f3}", "\u{2149}", "\u{1d457}", "\u{1d693}", "\u{02b2}", "\u{2c7c}", "\u{029d}", "\u{14a8}", "\u{2321}", "\u{fedf}", "\u{148e}", "\u{23ad}", "\u{1d36}", "\u{fee7}", "\u{06b5}", "\u{1da8}", "\u{06b6}", "\u{0692}", "\u{06ef}", "\u{076c}", "\u{14a9}", "\u{148f}", "\u{fb8c}", "\u{0691}", "\u{0632}", "\u{149b}", "\u{14b5}", "\u{fedd}", "\u{0644}", "\u{14b4}", "\u{fede}", "\u{1042}", "\u{0698}", "\u{0630}"], + "k": ["\u{1d458}", "\u{1d55c}", "\u{1d4c0}", "\u{1d48c}", "\u{1d4f4}", "\u{1d590}", "\u{ff4b}", "\u{1d528}", "\u{1d5c4}", "\u{1d62c}", "\u{1d5f8}", "\u{1d660}", "\u{1d424}", "\u{1d694}", "\u{1d4f}", "\u{0138}", "\u{049f}", "\u{2096}", "\u{03ba}", "\u{043a}", "\u{049a}", "\u{1030a}", "\u{df0a}", "\u{20ad}", "\u{049c}", "\u{04a0}", "\u{1d37}", "\u{1d0b}", "\u{1d779}", "\u{df79}", "\u{049b}", "\u{040c}", "\u{049e}", "\u{049d}", "\u{045c}", "\u{1d7b3}", "\u{dfb3}", "\u{051e}", "\u{1d6cb}", "\u{decb}", "\u{a5ea}", "\u{04a1}", "\u{1d84}"], + "l": ["\u{0406}", "\u{ff4c}", "\u{23fd}", "\u{05c0}", "\u{217c}", "\u{1d6b0}", "\u{05df}", "\u{1d7f7}", "\u{1d43c}", "\u{1d4d8}", "\u{1d591}", "\u{1d661}", "\u{01c0}", "\u{fe8d}", "\u{1d5a8}", "\u{2110}", "\u{1d529}", "\u{10320}", "\u{1d7ed}", "\u{a4f2}", "\u{1d425}", "\u{1d6ea}", "\u{1d610}", "\u{07ca}", "\u{1d724}", "\u{1d4c1}", "\u{1d798}", "\u{0627}", "\u{2d4f}", "\u{2160}", "\u{1d7cf}", "\u{1d62d}", "\u{1d5f9}", "\u{ffe8}", "\u{0031}", "\u{1d4f5}", "\u{16f28}", "\u{1d459}", "\u{2111}", "\u{1d48d}", "\u{2223}", "\u{10309}", "\u{04c0}", "\u{0049}", "\u{1ee00}", "\u{0196}", "\u{16c1}", "\u{1d540}", "\u{1d5dc}", "\u{1d55d}", "\u{1ee80}", "\u{ff29}", "\u{2113}", "\u{2c92}", "\u{1d7e3}", "\u{1d678}", "\u{1d574}", "\u{1d695}", "\u{0399}", "\u{1d644}", "\u{1028a}", "\u{1d7d9}", "\u{fe8e}", "\u{ff11}", "\u{1d75e}", "\u{06f1}", "\u{007c}", "\u{1e8c7}", "\u{05d5}", "\u{1d470}", "\u{1fbf1}", "\u{0661}", "\u{1d408}", "\u{1d5c5}", "\u{2503}", "\u{258f}", "\u{2575}", "\u{2595}", "\u{1963}", "\u{257d}", "\u{a7fe}", "\u{258e}", "\u{257f}", "\u{1d85}", "\u{230a}", "\u{2759}", "\u{23ae}", "\u{056c}", "\u{a646}", "\u{09f7}", "\u{1da9}", "\u{2502}", "\u{23a2}", "\u{02e1}", "\u{239c}", "\u{23a3}", "\u{2758}", "\u{2514}", "\u{2515}", "\u{23a9}", "\u{0285}", "\u{2590}", "\u{258c}", "\u{239f}", "\u{23a5}", "\u{23aa}", "\u{1968}", "\u{a716}", "\u{3057}", "\u{053c}", "\u{1d369}", "\u{df69}", "\u{002f}", "\u{0582}", "\u{14bb}", "\u{10483}", "\u{dc83}", "\u{1d38}", "\u{2e24}", "\u{1490}", "\u{239d}", "\u{2559}", "\u{21c2}", "\u{0c79}", "\u{0964}", "\u{4e28}", "\u{0f0d}", "\u{ff5c}", "\u{2016}", "\u{038a}", "\u{0e40}", "\u{0e44}", "\u{3134}", "\u{4e5a}", "\u{3163}"], + "m": ["\u{ff4d}", "\u{1d5fa}", "\u{ddfa}", "\u{1d426}", "\u{dc26}", "\u{1d5c6}", "\u{ddc6}", "\u{1d662}", "\u{de62}", "\u{217f}", "\u{1d62e}", "\u{de2e}", "\u{1d696}", "\u{de96}", "\u{20a5}", "\u{1d50}", "\u{0d28}", "\u{2098}", "\u{036b}", "\u{1d592}", "\u{dd92}", "\u{1320}", "\u{1dac}", "\u{10dd}", "\u{1d55e}", "\u{dd5e}", "\u{164f}", "\u{1662}", "\u{0d69}", "\u{1d48e}", "\u{dc8e}", "\u{1d6f}", "\u{163b}", "\u{2a4b}", "\u{15f6}", "\u{1d4f6}", "\u{dcf6}", "\u{264f}", "\u{1d45a}", "\u{dc5a}"], + "n": ["\u{1d45b}", "\u{1d5fb}", "\u{1d5c7}", "\u{1d48f}", "\u{ff4e}", "\u{1d593}", "\u{1d62f}", "\u{057c}", "\u{1d663}", "\u{0578}", "\u{1d52b}", "\u{1d4f7}", "\u{1d4c3}", "\u{1d697}", "\u{1d55f}", "\u{1d427}", "\u{03B7}", "\u{041f}", "\u{1d765}", "\u{df65}", "\u{043f}", "\u{014a}", "\u{10b6}", "\u{0548}", "\u{03a0}", "\u{220f}", "\u{2229}", "\u{144e}", "\u{207f}", "\u{1d776}", "\u{df76}", "\u{22c2}", "\u{0580}", "\u{05d7}", "\u{1d28}", "\u{2099}", "\u{0273}", "\u{1952}", "\u{014b}", "\u{fb28}", "\u{1d79f}", "\u{df9f}", "\u{05ea}", "\u{1d7b0}", "\u{dfb0}", "\u{054c}", "\u{1033f}", "\u{df3f}", "\u{0572}", "\u{10340}", "\u{df40}", "\u{2ca0}", "\u{10d8}", "\u{1965}", "\u{1970}", "\u{1fc3}", "\u{10490}", "\u{dc90}", "\u{0525}", "\u{1459}", "\u{144f}", "\u{145a}", "\u{1d752}", "\u{df52}", "\u{0508}", "\u{1d7c6}", "\u{dfc6}", "\u{0564}"], + "o": ["\u{feeb}", "\u{1d6b6}", "\u{2c9f}", "\u{0c82}", "\u{0030}", "\u{118d7}", "\u{1d490}", "\u{1d7f6}", "\u{fba6}", "\u{10404}", "\u{1d7d8}", "\u{a4f3}", "\u{0555}", "\u{1d6d0}", "\u{1d77e}", "\u{fbab}", "\u{10ff}", "\u{1d560}", "\u{1d698}", "\u{1d4de}", "\u{0665}", "\u{1d52c}", "\u{0966}", "\u{041e}", "\u{1d40e}", "\u{104c2}", "\u{ff2f}", "\u{1d7bc}", "\u{1d748}", "\u{1d7ce}", "\u{1d4f8}", "\u{1d764}", "\u{0b66}", "\u{1d442}", "\u{1d630}", "\u{0585}", "\u{1d5ae}", "\u{1042c}", "\u{0647}", "\u{1d594}", "\u{0d20}", "\u{118b5}", "\u{1d782}", "\u{104ea}", "\u{0ed0}", "\u{0c66}", "\u{1d6f0}", "\u{3007}", "\u{09e6}", "\u{1d70a}", "\u{1d11}", "\u{1d428}", "\u{0d82}", "\u{1d476}", "\u{1d7b8}", "\u{114d0}", "\u{0d02}", "\u{1d5fc}", "\u{fba7}", "\u{0b20}", "\u{06d5}", "\u{1d45c}", "\u{fbaa}", "\u{10292}", "\u{1d546}", "\u{1d5e2}", "\u{1d67e}", "\u{1d72a}", "\u{ab3d}", "\u{1ee24}", "\u{06be}", "\u{03bf}", "\u{0d66}", "\u{feea}", "\u{10516}", "\u{118c8}", "\u{2134}", "\u{1ee64}", "\u{1d70e}", "\u{ff4f}", "\u{06f5}", "\u{1d616}", "\u{1d0f}", "\u{043e}", "\u{1d57a}", "\u{ff10}", "\u{1d7e2}", "\u{06c1}", "\u{1d4aa}", "\u{0ce6}", "\u{2c9e}", "\u{118e0}", "\u{2d54}", "\u{1040}", "\u{1d512}", "\u{fbac}", "\u{0be6}", "\u{0c02}", "\u{1d744}", "\u{101d}", "\u{1d664}", "\u{0ae6}", "\u{039f}", "\u{fbad}", "\u{fba9}", "\u{0a66}", "\u{03c3}", "\u{12d0}", "\u{1d5c8}", "\u{05e1}", "\u{fba8}", "\u{fee9}", "\u{1d79e}", "\u{feec}", "\u{1d7ec}", "\u{07c0}", "\u{1d6d4}", "\u{1d64a}", "\u{0e50}", "\u{1ee84}", "\u{1fbf0}", "\u{102ab}", "\u{004f}", "\u{2092}", "\u{1d3c}", "\u{2070}", "\u{07cb}", "\u{1d52}", "\u{2b58}", "\u{26ac}", "\u{2080}", "\u{2b55}", "\u{104a0}", "\u{dca0}", "\u{03b8}", "\u{25cb}", "\u{047a}", "\u{1d7b1}", "\u{dfb1}", "\u{1d767}", "\u{df67}", "\u{274d}", "\u{2688}", "\u{1d777}", "\u{df77}", "\u{25ef}", "\u{1d7a1}", "\u{dfa1}", "\u{1d6c9}", "\u{dec9}", "\u{0298}", "\u{047b}", "\u{0718}", "\u{a668}", "\u{0398}", "\u{1d703}", "\u{df03}", "\u{2d40}", "\u{2d59}", "\u{038c}", "\u{0424}", "\u{1d73d}", "\u{df3d}", "\u{2689}", "\u{12d1}", "\u{2299}", "\u{25ce}", "\u{0e4f}", "\u{0fc0}"], + "p": ["\u{1d4c5}", "\u{1d665}", "\u{1d754}", "\u{1d45d}", "\u{1d52d}", "\u{1d6d2}", "\u{1d5c9}", "\u{03c1}", "\u{1d429}", "\u{1d595}", "\u{0440}", "\u{1d5fd}", "\u{1d746}", "\u{2374}", "\u{1d4f9}", "\u{1d7c8}", "\u{1d7ba}", "\u{1d780}", "\u{1d6e0}", "\u{03f1}", "\u{1d491}", "\u{1d699}", "\u{ff50}", "\u{1d631}", "\u{1d561}", "\u{1d78e}", "\u{1d70c}", "\u{2ca3}", "\u{1d71a}"], + "q": ["\u{0566}", "\u{1d562}", "\u{1d632}", "\u{051b}", "\u{1d52e}", "\u{1d596}", "\u{1d666}", "\u{1d492}", "\u{1d5fe}", "\u{0563}", "\u{1d42a}", "\u{1d5ca}", "\u{1d45e}", "\u{1d4fa}", "\u{1d4c6}", "\u{1d69a}", "\u{ff51}"], + "r": ["\u{1d563}", "\u{ab47}", "\u{1d597}", "\u{1d26}", "\u{ab48}", "\u{1d493}", "\u{1d633}", "\u{0433}", "\u{1d4c7}", "\u{2c85}", "\u{1d45f}", "\u{ab81}", "\u{ff52}", "\u{1d42b}", "\u{1d52f}", "\u{1d69b}", "\u{1d5ff}", "\u{1d5cb}", "\u{1d4fb}", "\u{1d667}"], + "s": ["\u{0455}", "\u{1d598}", "\u{ff53}", "\u{1d530}", "\u{1d460}", "\u{01bd}", "\u{a731}", "\u{1d5cc}", "\u{1d668}", "\u{10448}", "\u{1d564}", "\u{1d494}", "\u{1d4fc}", "\u{abaa}", "\u{1d4c8}", "\u{1d69c}", "\u{118c1}", "\u{1d42c}", "\u{1d634}", "\u{1d600}", "\u{00A7}"], + "t": ["\u{1d42d}", "\u{ff54}", "\u{1d5cd}", "\u{1d669}", "\u{1d69d}", "\u{1d4fd}", "\u{1d461}", "\u{1d4c9}", "\u{1d599}", "\u{1d601}", "\u{1d531}", "\u{1d635}", "\u{1d495}", "\u{1d565}"], + "u": ["\u{1d602}", "\u{ab52}", "\u{1d7be}", "\u{1d1c}", "\u{028b}", "\u{104f6}", "\u{118d8}", "\u{a79f}", "\u{ab4e}", "\u{1d74a}", "\u{1d59a}", "\u{1d6d6}", "\u{1d4ca}", "\u{1d4fe}", "\u{ff55}", "\u{1d462}", "\u{1d636}", "\u{1d532}", "\u{057d}", "\u{1d566}", "\u{03c5}", "\u{1d69e}", "\u{1d42e}", "\u{1d710}", "\u{1d66a}", "\u{1d5ce}", "\u{1d496}", "\u{1d784}"], + "v": ["\u{05d8}", "\u{1d4cb}", "\u{1d637}", "\u{1d6ce}", "\u{1d42f}", "\u{1d533}", "\u{aba9}", "\u{03bd}", "\u{1d4ff}", "\u{1d59b}", "\u{0475}", "\u{1d603}", "\u{22c1}", "\u{1d463}", "\u{1d708}", "\u{118c0}", "\u{1d742}", "\u{ff56}", "\u{1d497}", "\u{2174}", "\u{1d5cf}", "\u{1d66b}", "\u{1d7b6}", "\u{11706}", "\u{1d567}", "\u{1d77c}", "\u{2228}", "\u{1d20}", "\u{1d69f}"], + "w": ["\u{1d500}", "\u{1d5d0}", "\u{0461}", "\u{026f}", "\u{1d66c}", "\u{1d6a0}", "\u{1d604}", "\u{1d568}", "\u{1d498}", "\u{0561}", "\u{1d4cc}", "\u{1d59c}", "\u{1170e}", "\u{1170f}", "\u{ab83}", "\u{1170a}", "\u{1d464}", "\u{1d534}", "\u{ff57}", "\u{1d21}", "\u{1d638}", "\u{051d}", "\u{1d430}"], + "x": ["\u{1d605}", "\u{1d535}", "\u{1d59d}", "\u{1d66d}", "\u{157d}", "\u{166e}", "\u{ff58}", "\u{1d501}", "\u{2a2f}", "\u{0445}", "\u{292c}", "\u{00d7}", "\u{1541}", "\u{1d569}", "\u{2179}", "\u{1d639}", "\u{1d431}", "\u{1d4cd}", "\u{1d5d1}", "\u{292b}", "\u{1d6a1}", "\u{1d499}", "\u{1d465}"], + "y": ["\u{1d59e}", "\u{1d502}", "\u{0443}", "\u{1d466}", "\u{118dc}", "\u{0263}", "\u{1d49a}", "\u{1d6fe}", "\u{213d}", "\u{1d56a}", "\u{1d606}", "\u{1d5d2}", "\u{1d772}", "\u{04af}", "\u{1d63a}", "\u{1d8c}", "\u{1d432}", "\u{ab5a}", "\u{1d738}", "\u{1d4ce}", "\u{028f}", "\u{1d536}", "\u{1d6c4}", "\u{03b3}", "\u{ff59}", "\u{1eff}", "\u{1d6a2}", "\u{1d66e}", "\u{10e7}", "\u{1d7ac}", "\u{02b8}", "\u{04b1}", "\u{1d67}", "\u{1d5e}", "\u{10345}", "\u{df45}", "\u{10b8}", "\u{10f8}", "\u{104a6}", "\u{dca6}", "\u{10c4}", "\u{10be}", "\u{07cc}", "\u{05e5}", "\u{045f}", "\u{05e2}", "\u{1048b}", "\u{dc8b}", "\u{10e3}", "\u{4e2b}", "\u{038e}", "\u{1f1fe}", "\u{040E}"], + "z": ["\u{1d4cf}", "\u{1d49b}", "\u{1d63b}", "\u{1d607}", "\u{1d22}", "\u{1d56b}", "\u{1d537}", "\u{1d5d3}", "\u{1d433}", "\u{1d467}", "\u{1d66f}", "\u{1d503}", "\u{ab93}", "\u{ff5a}", "\u{118c4}", "\u{1d6a3}", "\u{1d59f}"], +}; + +class Homoglyph { + replace(text, charMap = HOMOGLYPHS) { + const output = []; + const inputTextSymbolArray = this.makeSymbolArray(removeDiacritics(text)); + const keys = Object.keys(charMap); + for (const char of inputTextSymbolArray) { + let found = false; + for (let i = keys.length - 1; i > 0; i--) { + if (charMap[keys[i]].includes(char)) { + output.push(keys[i]); + found = true; + break; + } + } + if (!found) output.push(char); + } + + return Tools.toHomoglyphWord(output.join("")); + } + + testReplace(text, charMap = HOMOGLYPHS) { + const output = []; + const inputTextSymbolArray = this.makeSymbolArray(removeDiacritics(text)); + const keys = Object.keys(charMap); + for (const char of inputTextSymbolArray) { + let found = false; + for (let i = keys.length - 1; i > 0; i--) { + if (charMap[keys[i]].includes(char)) { + output.push(keys[i]); + found = true; + break; + } + } + if (!found) output.push(char); + } + + return output.join(""); + } + + deepfry(text, charmap = HOMOGLYPHS) { + const output = []; + const inputTextSymbolArray = this.makeSymbolArray(text); + for (let i = 0; i < inputTextSymbolArray.length; i++) { + if (charmap[inputTextSymbolArray[i]]) { + output.push(Tools.sampleOne(charmap[inputTextSymbolArray[i]])); + } else { + output.push(inputTextSymbolArray[i]); + } + } + + return output.join(""); + } + + makeSymbolArray(txt) { + const a = []; + let s; + for (s of txt) { + a.push(s); + } + return a; + } +} + +module.exports = new Homoglyph(); diff --git a/sources/rng/lcrng.js b/sources/rng/lcrng.js new file mode 100644 index 0000000..58d9cd3 --- /dev/null +++ b/sources/rng/lcrng.js @@ -0,0 +1,189 @@ +"use strict"; + +const converter = require("hex2dec"); + +const natures = [ + "Hardy", "Lonely", "Brave", "Adamant", "Naughty", + "Bold", "Docile", "Relaxed", "Impish", "Lax", + "Timid", "Hasty", "Serious", "Jolly", "Naive", + "Modest", "Mild", "Quiet", "Bashful", "Rash", + "Calm", "Gentle", "Sassy", "Careful", "Quirky", +]; + +class LCRNG { + constructor(seed, add, mult) { + this.seed = seed; + this.add = add; + this.mult = mult; + this.natures = natures; + this.right = 0; + this.val = 0; + this.flags = []; + this.low8 = []; + for (let i = 0; i < 256; i++) { + this.right = converter.decToHex(`${this.unsign(0x41c64e6d * i + 0x6073)}`); + this.val = this.right >>> 16; + this.flags[this.val] = true; + this.low8[this.val] = i; + this.val--; + this.flags[this.val] = true; + this.low8[this.val] = i; + } + } + + getNext32BitNumber(seed = this.seed, times = 1) { + for (let i = 0; i < times; i++) { + seed = this.unsign(Math.imul(seed, this.mult)); + seed = this.unsign(seed + this.add); + seed = this.unsign(seed & 0xFFFFFFFF); + this.seed = seed; + } + return converter.decToHex(`${seed}`); + } + + getNext16BitNumber(seed = this.seed) { + return converter.decToHex(`${this.getNext32BitNumber(seed) >>> 16}`); + } + + calcMethod124SeedIVs(pid) { + const ret = []; + const pidl = this.unsign((pid & 0xFFFF) << 16); + const pidh = pid & 0xFFFF0000; + + let k1 = this.unsign(pidh - pidl * 0x41c64e6d); + for (let cnt = 0; cnt < 256; ++cnt, k1 = this.unsign(k1 - 0xc64e6d00)) { + const test = k1 >>> 16; + if (this.flags[test]) { + const fullFirst = this.unsign(pidl | (cnt << 8) | this.low8[test]); + const fullSecond = this.getNext32BitNumber(fullFirst); + if ((fullSecond & 0xFFFF0000) === pidh) { + ret.push([`${converter.decToHex(`${this.reverse(fullFirst)}`)}`, `${this.getNext32BitNumber(fullSecond)}`]); + } + } + } + return ret; + } + + calcMethodXDSeedIVs(pid) { + const ret = []; + const first = pid & 0xFFFF0000; + const second = (pid & 0xFFFF) << 16; + let fullFirst; + + let t = ((second - 0x343fd * first) - 0x259ec4) & 0xFFFFFFFF; + const kmax = (0x343fabc02 - t) / 0x100000000; + + for (let k = 0; k <= kmax; k++, t += 0x100000000) { + if (this.unsign(t % 0x343fd) < 0x10000) { + fullFirst = this.unsign(first | (t / 0x343fd)); + const iv2 = this.reverseXD(this.reverseXD(fullFirst)); + const iv1 = this.reverseXD(iv2); + ret.push([`${converter.decToHex(`${this.reverseXD(iv1)}`)}`, iv1, iv2, 0]); + } + } + return ret; + } + + calcMethodChannelSeedIVs(pid) { + const ret = []; + const first = (pid & 0xFFFF0000) ^ 0x80000000; + const second = (pid & 0xFFFF) << 16; + let fullFirst; + + let t = ((second - 0x343fd * first) - 0x259ec4) & 0xFFFFFFFF; + t = t < 0 ? t + 0x100000000 : t; + const kmax = (0x343fabc02 - t) / 0x100000000; + + for (let k = 0; k <= kmax; k++, t += 0x100000000) { + if (this.unsign(t % 0x343fd) < 0x10000) { + fullFirst = this.unsign(first | (t / 0x343fd)); + const seed = this.reverseXD(this.reverseXD(fullFirst)); + ret.push([`${converter.decToHex(`${seed}`)}`, this.unsign(this.unsign(fullFirst * 0x284A930D) + 0xA2974C77), 0, 1]); + } + } + return ret; + } + + reverse(seed) { + seed = this.unsign(Math.imul(seed, 0xeeb9eb65)); + seed = this.unsign(seed + 0xa3561a1); + seed = this.unsign(seed & 0xFFFFFFFF); + return seed; + } + + reverseXD(seed) { + seed = this.unsign(Math.imul(seed, 0xB9B33155)); + seed = this.unsign(seed + 0xA170F641); + seed = this.unsign(seed & 0xFFFFFFFF); + return seed; + } + + concat16(args) { + let ret = "0x"; + for (let i = 0; i < args.length; i++) { + ret += args[i].replace("0x", "").padStart(4, "0"); + } + return ret; + } + // From X-Act IVs to PID applet + unsign(int) { + return (int >>> 1) * 2 + (int & 1); + } +} + +// https://github.com/Admiral-Fish/RNGReporter/blob/master/RNGReporter/Objects/LCRNG.cs#L80 +class PokeRNG extends LCRNG { + constructor(add, mult) { + super(add, mult); + this.add = 0x6073; + this.mult = 0x41C64E6D; + } +} + +class PokeRNGR extends LCRNG { + constructor(add, mult) { + super(add, mult); + this.add = 0xa3561a1; + this.mult = 0xeeb9eb65; + } +} + +class XDRNG extends LCRNG { + constructor(add, mult) { + super(add, mult); + this.add = 0x269EC3; + this.mult = 0x343FD; + } +} + +class XDRNGR extends LCRNG { + constructor(add, mult) { + super(add, mult); + this.add = 0xA170F641; + this.mult = 0xB9B33155; + } +} + +class ARNG extends LCRNG { + constructor(add, mult) { + super(add, mult); + this.add = 0x01; + this.mult = 0x6c078965; + } +} + +class ARNGR extends LCRNG { + constructor(add, mult) { + super(add, mult); + this.add = 0x69c77f93; + this.mult = 0x9638806d; + } +} + +module.exports.LCRNG = LCRNG; +module.exports.PokeRNG = PokeRNG; +module.exports.PokeRNGR = PokeRNGR; +module.exports.XDRNG = XDRNG; +module.exports.XDRNGR = XDRNGR; +module.exports.ARNG = ARNG; +module.exports.ARNGR = ARNGR; diff --git a/sources/storage.js b/sources/storage.js index d538c1d..0d2e8fd 100644 --- a/sources/storage.js +++ b/sources/storage.js @@ -35,7 +35,7 @@ class Storage { importDatabase(roomid) { let file = "{}"; try { - file = fs.readFileSync("./databases/" + roomid + ".json").toString(); + file = fs.readFileSync(`./databases/${roomid}.json`).toString(); } catch (e) {} this.databases[roomid] = JSON.parse(file); } @@ -45,7 +45,7 @@ class Storage { */ exportDatabase(roomid) { if (!(roomid in this.databases)) return; - fs.writeFileSync("./databases/" + roomid + ".json", JSON.stringify(this.databases[roomid])); + fs.writeFileSync(`./databases/${roomid}.json`, JSON.stringify(this.databases[roomid])); } importDatabases() { diff --git a/sources/tools-data.js b/sources/tools-data.js index 78069e4..0a621ea 100644 --- a/sources/tools-data.js +++ b/sources/tools-data.js @@ -265,7 +265,7 @@ class Pokemon extends Effect { * species and forme. * @type {string} */ - this.spriteid = this.spriteid || (Tools.toId(this.baseSpecies) + (this.baseSpecies !== this.name ? "-" + Tools.toId(this.forme) : "")); + this.spriteid = this.spriteid || (Tools.toId(this.baseSpecies) + (this.baseSpecies !== this.name ? `-${Tools.toId(this.forme)}` : "")); /** * Abilities diff --git a/sources/tools.js b/sources/tools.js index 421dbeb..ccae7ab 100644 --- a/sources/tools.js +++ b/sources/tools.js @@ -11,10 +11,17 @@ const https = require("https"); const url = require("url"); const Data = require("./tools-data"); +const groups = require("../showdown/src/groups.json"); const whitespaceRegex = new RegExp("\\s+", "g"); const nullCharactersRegex = new RegExp("[\u0000\u200B-\u200F]+", "g"); +const dateOptions = { + year: "numeric", + month: "numeric", + day: "numeric", +}; + /** * @typedef Learnset * @type {Object} @@ -94,7 +101,7 @@ class Tools { loadData() { let typeChart; try { - typeChart = require(this.dataFilePath + "typechart.js").BattleTypeChart; + typeChart = require(`${this.dataFilePath}typechart.js`).BattleTypeChart; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -122,7 +129,7 @@ class Tools { let pokedex; try { - pokedex = require(this.dataFilePath + "pokedex.js").BattlePokedex; + pokedex = require(`${this.dataFilePath}pokedex.js`).BattlePokedex; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -136,7 +143,7 @@ class Tools { let moves; try { - moves = require(this.dataFilePath + "moves.js").BattleMovedex; + moves = require(`${this.dataFilePath}moves.js`).BattleMovedex; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -150,7 +157,7 @@ class Tools { let items; try { - items = require(this.dataFilePath + "items.js").BattleItems; + items = require(`${this.dataFilePath}items.js`).BattleItems; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -164,7 +171,7 @@ class Tools { let abilities; try { - abilities = require(this.dataFilePath + "abilities.js").BattleAbilities; + abilities = require(`${this.dataFilePath}abilities.js`).BattleAbilities; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -176,7 +183,7 @@ class Tools { loadAliases() { let aliases; try { - aliases = require(this.dataFilePath + "aliases.js").BattleAliases; + aliases = require(`${this.dataFilePath}aliases.js`).BattleAliases; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -190,7 +197,7 @@ class Tools { let learnsets; try { - learnsets = require(this.dataFilePath + "learnsets.js").BattleLearnsets; + learnsets = require(`${this.dataFilePath}learnsets.js`).BattleLearnsets; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -204,7 +211,7 @@ class Tools { let formatsData; try { - formatsData = require(this.dataFilePath + "formats-data.js").BattleFormatsData; + formatsData = require(`${this.dataFilePath}formats-data.js`).BattleFormatsData; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -216,7 +223,7 @@ class Tools { loadBadges() { let badges; try { - badges = require(this.dataFilePath + "badges.js").BattleBadges; + badges = require(`${this.dataFilePath}badges.js`).BattleBadges; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -228,7 +235,7 @@ class Tools { loadCharacters() { let characters; try { - characters = require(this.dataFilePath + "characters.js").BattleCharacters; + characters = require(`${this.dataFilePath}characters.js`).BattleCharacters; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -240,7 +247,7 @@ class Tools { loadTeams() { let teams; try { - teams = require(this.dataFilePath + "teams.js").BattlePokeTeams; + teams = require(`${this.dataFilePath}teams.js`).BattlePokeTeams; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -252,7 +259,7 @@ class Tools { loadTrainerClasses() { let trainerClasses; try { - trainerClasses = require(this.dataFilePath + "trainer-classes.js").BattleTrainerClasses; + trainerClasses = require(`${this.dataFilePath}trainer-classes.js`).BattleTrainerClasses; } catch (e) { if (e.code !== "MODULE_NOT_FOUND") { throw e; @@ -270,7 +277,7 @@ class Tools { const type = typeof text; if (type !== "string") { if (type === "number") { - text = "" + text; + text = `${text}`; } else { if (text.id) { text = text.id; @@ -288,6 +295,14 @@ class Tools { return text.toLowerCase().replace(/[^a-z0-9]/g, ""); } + toFilterWord(text) { + return text.toLowerCase().replace(/[^a-z0-9+-\\^$.|?*()[]{}]/g, ""); + } + + toHomoglyphWord(text) { + return text.toLowerCase().replace(/[^a-z0-9 ?]/g, ""); + } + /** * @param {any} text * @return {string} @@ -297,7 +312,7 @@ class Tools { const type = typeof text; if (type !== "string") { if (type === "number") { - text = "" + text; + text = `${text}`; } else { if (text.name) { text = text.name; @@ -306,7 +321,7 @@ class Tools { } } } - if (Config.groups && text.charAt(0) in Config.groups) text = text.substr(1); + if (groups && text.charAt(0) in groups) text = text.substr(1); // Crop out the status (which occurs after @) const n = text.indexOf("@"); text = text.substring(0, n !== -1 ? n : text.length); @@ -320,11 +335,36 @@ class Tools { toString(text) { const type = typeof text; if (type === "string") return text; - if (type === "number") return "" + text; + if (type === "number") return `${text}`; if (!text) return ""; return (text.toString ? text.toString() : JSON.stringify(text)); } + discordText() { + const d = new Date(); + return `[${d.toLocaleDateString("en-AU", dateOptions)} ${d.toTimeString().split(" ")[0]}] `.grey + `Discord-Bot: `.yellow; + } + + moodeText() { + const d = new Date(); + return `[${d.toLocaleDateString("en-AU", dateOptions)} ${d.toTimeString().split(" ")[0]}] `.grey + `moodE: `.yellow; + } + + pokemonShowdownText() { + const d = new Date(); + return `[${d.toLocaleDateString("en-AU", dateOptions)} ${d.toTimeString().split(" ")[0]}] `.grey + `pokemon-showdown: `.yellow; + } + + showdownText() { + const d = new Date(); + return `[${d.toLocaleDateString("en-AU", dateOptions)} ${d.toTimeString().split(" ")[0]}] `.grey + `PS-Bot: `.yellow; + } + + twitchText() { + const d = new Date(); + return `[${d.toLocaleDateString("en-AU", dateOptions)} ${d.toTimeString().split(" ")[0]}] `.grey + `Twitch-Bot: `.yellow; + } + /** * @param {any} text * @return {string} @@ -335,6 +375,22 @@ class Tools { return text.replace(/[^a-zA-Z0-9 ]/g, "").trim(); } + parseUsernameText(usernameText) { + let away = false; + let status = ""; + let username = ""; + const atIndex = usernameText.indexOf("@"); + if (atIndex !== -1) { + username = usernameText.substr(0, atIndex); + status = usernameText.substr(atIndex + 1); + away = status.charAt(0) === "!"; + } else { + username = usernameText; + } + + return {away, status, username}; + } + /** * @param {string} text * @return {string} @@ -354,10 +410,10 @@ class Tools { if (list.length === 1) { return formatting + list[0] + formatting; } else if (list.length === 2) { - return formatting + list[0] + formatting + " and " + formatting + list[1] + formatting; + return `${formatting + list[0] + formatting} and ${formatting}${list[1]}${formatting}`; } else { const len = list.length - 1; - return formatting + list.slice(0, len).join(formatting + ", " + formatting) + formatting + ", and " + formatting + list[len] + formatting; + return `${formatting + list.slice(0, len).join(`${formatting}, ${formatting}`) + formatting}, and ${formatting}${list[len]}${formatting}`; } } @@ -369,14 +425,14 @@ class Tools { joinListHtml(list, tag) { if (!list.length) return ""; const openingTag = tag; - const closingTag = "/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/"); + return (`${str}`).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/"); } /** @@ -395,7 +451,7 @@ class Tools { */ unescapeHTML(str) { if (!str) return ""; - return ("" + str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(///g, "/").replace(/'/g, "'").replace(/"/g, "\""); + return (`${str}`).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(///g, "/").replace(/'/g, "'").replace(/"/g, "\""); } /** @@ -407,12 +463,12 @@ class Tools { text = this.toString(text); if (!text) return ""; text = text.trim(); - if (text.startsWith("/wall ")) text = "/announce " + text.substr(6); - if (text.startsWith("/announce ") && (!room || !Users.self.hasRank(room, "%"))) { + if (text.startsWith("/wall ")) text = `/announce ${text.substr(6)}`; + if (text.startsWith("/announce ") && (!room || !psUsers.self.hasRoomRank(room, "%"))) { text = text.substr(10); - if (!text.includes("**") && text.length <= 296) text = "**" + text + "**"; + if (!text.includes("**") && text.length <= 296) text = `**${text}**`; } - if (text.length > 300) text = text.substr(0, 297) + "..."; + if (text.length > 300) text = `${text.substr(0, 297)}...`; return text; } @@ -499,7 +555,7 @@ class Tools { } uncacheDir(root) { - const absoluteRoot = path.resolve(__dirname, "../" + root); + const absoluteRoot = path.resolve(__dirname, `../${root}`); for (const key in require.cache) { if (key.startsWith(absoluteRoot)) { delete require.cache[key]; @@ -523,14 +579,14 @@ class Tools { if (id === "constructor") return null; if (!(id in this.data.pokedex)) { let aliasTo = ""; - if (id.startsWith("mega") && this.data.pokedex[id.slice(4) + "mega"]) { - aliasTo = id.slice(4) + "mega"; - } else if (id.startsWith("m") && this.data.pokedex[id.slice(1) + "mega"]) { - aliasTo = id.slice(1) + "mega"; - } else if (id.startsWith("primal") && this.data.pokedex[id.slice(6) + "primal"]) { - aliasTo = id.slice(6) + "primal"; - } else if (id.startsWith("p") && this.data.pokedex[id.slice(1) + "primal"]) { - aliasTo = id.slice(1) + "primal"; + if (id.startsWith("mega") && this.data.pokedex[`${id.slice(4)}mega`]) { + aliasTo = `${id.slice(4)}mega`; + } else if (id.startsWith("m") && this.data.pokedex[`${id.slice(1)}mega`]) { + aliasTo = `${id.slice(1)}mega`; + } else if (id.startsWith("primal") && this.data.pokedex[`${id.slice(6)}primal`]) { + aliasTo = `${id.slice(6)}primal`; + } else if (id.startsWith("p") && this.data.pokedex[`${id.slice(1)}primal`]) { + aliasTo = `${id.slice(1)}primal`; } if (aliasTo) { const pokemon = this.getPokemon(aliasTo); @@ -566,7 +622,7 @@ class Tools { */ getExistingPokemon(name) { const pokemon = this.getPokemon(name); - if (!pokemon) throw new Error("Expected Pokemon for '" + name + "'"); + if (!pokemon) throw new Error(`Expected Pokemon for '${name}'`); return pokemon; } @@ -595,7 +651,7 @@ class Tools { */ getExistingMove(name) { const move = this.getMove(name); - if (!move) throw new Error("Expected move for '" + name + "'"); + if (!move) throw new Error(`Expected move for '${name}'`); return move; } @@ -624,7 +680,7 @@ class Tools { */ getExistingItem(name) { const item = this.getItem(name); - if (!item) throw new Error("Expected item for '" + name + "'"); + if (!item) throw new Error(`Expected item for '${name}'`); return item; } @@ -653,7 +709,7 @@ class Tools { */ getExistingAbility(name) { const ability = this.getAbility(name); - if (!ability) throw new Error("Expected ability for '" + name + "'"); + if (!ability) throw new Error(`Expected ability for '${name}'`); return ability; } @@ -668,13 +724,13 @@ class Tools { name = this.data.aliases[id]; id = this.toId(name); } - if ("gen" + this.gen + id in MessageParser.formatsData) { - id = "gen" + this.gen + id; + if (`gen${this.gen}${id}` in psMessageParser.formatsData) { + id = `gen${this.gen}${id}`; } - if (id === "constructor" || !(id in MessageParser.formatsData)) return null; + if (id === "constructor" || !(id in psMessageParser.formatsData)) return null; let format = this.FormatCache.get(id); if (format) return format; - format = new Data.Format(name, MessageParser.formatsData[id]); + format = new Data.Format(name, psMessageParser.formatsData[id]); this.FormatCache.set(id, format); return format; } @@ -685,7 +741,7 @@ class Tools { */ getExistingFormat(name) { const format = this.getFormat(name); - if (!format) throw new Error("Expected format for '" + name + "'"); + if (!format) throw new Error(`Expected format for '${name}'`); return format; } @@ -772,8 +828,8 @@ class Tools { const positiveIndex = parts.findIndex(elem => elem > 0); const precision = (options && options.precision ? options.precision : parts.length); if (options && options.hhmmss) { - const string = parts.slice(positiveIndex).map(value => value < 10 ? "0" + value : "" + value).join(":"); - return string.length === 2 ? "00:" + string : string; + const string = parts.slice(positiveIndex).map(value => value < 10 ? `0${value}` : `${value}`).join(":"); + return string.length === 2 ? `00:${string}` : string; } // round least significant displayed unit if (positiveIndex + precision < parts.length && precision > 0 && positiveIndex >= 0) { @@ -781,7 +837,7 @@ class Tools { parts[positiveIndex + precision - 1]++; } } - return parts.slice(positiveIndex).reverse().map((value, index) => value ? value + " " + unitNames[index] + (value > 1 ? "s" : "") : "").reverse().slice(0, precision).join(" ").trim(); + return parts.slice(positiveIndex).reverse().map((value, index) => value ? `${value} ${unitNames[index]}${value > 1 ? "s" : ""}` : "").reverse().slice(0, precision).join(" ").trim(); } /** @@ -810,16 +866,16 @@ class Tools { key = pageData.key; } catch (e) { if (/^[^<]*/.test(data)) { - return callback("Cloudflare-related error uploading to Hastebin: " + e.message); + return callback(`Cloudflare-related error uploading to Hastebin: ${e.message}`); } else { - return callback("Unknown error uploading to Hastebin: " + e.message); + return callback(`Unknown error uploading to Hastebin: ${e.message}`); } } - callback("https://hastebin.com/raw/" + key); + callback(`https://hastebin.com/raw/${key}`); }); }); - request.on("error", error => console.log("Login error: " + error.stack)); + request.on("error", error => console.log(`Login error: ${error.stack}`)); if (text) request.write(text); request.end(); @@ -866,6 +922,214 @@ class Tools { this.deepFreeze(this.data); } + + // many thanks to fart (@tmagicturtle) for the following + hash(text) { + const MD5 = function (f) { + function i(b, c) { + const f = b & 2147483648; + const g = c & 2147483648; + const d = b & 1073741824; + const e = c & 1073741824; + const h = (b & 1073741823) + (c & 1073741823); + return d & e ? h ^ 2147483648 ^ f ^ g : d | e ? h & 1073741824 ? h ^ 3221225472 ^ f ^ g : h ^ 1073741824 ^ f ^ g : h ^ f ^ g; + } + function j(b, c, d, e, f, g, h) { + b = i(b, i(i(c & d | ~c & e, f), h)); + return i(b << g | b >>> 32 - g, c); + } + function k(b, c, d, e, f, g, h) { + b = i(b, i(i(c & e | d & ~e, f), h)); + return i(b << g | b >>> 32 - g, c); + } + function l(b, c, e, d, f, g, h) { + b = i(b, i(i(c ^ e ^ d, f), h)); + return i(b << g | b >>> 32 - g, c); + } + function m(b, c, e, d, f, g, h) { + b = i(b, i(i(e ^ (c | ~d), f), h)); + return i(b << g | b >>> 32 - g, c); + } + function n(b) { + let c = ""; + let e = ""; + let d; + for (d = 0; d <= 3; d++) { + e = b >>> d * 8 & 255; + e = `0${e.toString(16)}`; + c += e.substr(e.length - 2, 2); + } + return c; + } + let g = []; + let o, p, q, r, b, c, d, e; + f = (function (b) { + for (b = b.replace(/\r\n/g, "\n"), c = "", e = 0; e < b.length; e++) { + const d = b.charCodeAt(e); + if (d < 128) { + c += String.fromCharCode(d); + } else { + if (d > 127 && d < 2048) { + c += String.fromCharCode(d >> 6 | 192); + } else { + c += String.fromCharCode(d >> 12 | 224); + c += String.fromCharCode(d >> 6 & 63 | 128); + c += String.fromCharCode(d & 63 | 128); + } + } + } return c; + })(f); + g = (function (b) { + let c; + d = b.length; + c = d + 8; + const e = ((c - c % 64) / 64 + 1) * 16; + const f = Array(e - 1); + let g = 0; + let h = 0; + for (h; h < d; h++) { + c = (h - h % 4) / 4; + g = h % 4 * 8; + f[c] |= b.charCodeAt(h) << g; + } + f[(h - h % 4) / 4] |= 128 << h % 4 * 8; + f[e - 2] = d << 3; + f[e - 1] = d >>> 29; + return f; + })(f); + b = 1732584193; + c = 4023233417; + d = 2562383102; + e = 271733878; + for (f = 0; f < g.length; f += 16) { + o = b; + p = c; + q = d; + r = e; + b = j(b, c, d, e, g[f + 0], 7, 3614090360); + e = j(e, b, c, d, g[f + 1], 12, 3905402710); + d = j(d, e, b, c, g[f + 2], 17, 606105819); + c = j(c, d, e, b, g[f + 3], 22, 3250441966); + b = j(b, c, d, e, g[f + 4], 7, 4118548399); + e = j(e, b, c, d, g[f + 5], 12, 1200080426); + d = j(d, e, b, c, g[f + 6], 17, 2821735955); + c = j(c, d, e, b, g[f + 7], 22, 4249261313); + b = j(b, c, d, e, g[f + 8], 7, 1770035416); + e = j(e, b, c, d, g[f + 9], 12, 2336552879); + d = j(d, e, b, c, g[f + 10], 17, 4294925233); + c = j(c, d, e, b, g[f + 11], 22, 2304563134); + b = j(b, c, d, e, g[f + 12], 7, 1804603682); + e = j(e, b, c, d, g[f + 13], 12, 4254626195); + d = j(d, e, b, c, g[f + 14], 17, 2792965006); + c = j(c, d, e, b, g[f + 15], 22, 1236535329); + b = k(b, c, d, e, g[f + 1], 5, 4129170786); + e = k(e, b, c, d, g[f + 6], 9, 3225465664); + d = k(d, e, b, c, g[f + 11], 14, 643717713); + c = k(c, d, e, b, g[f + 0], 20, 3921069994); + b = k(b, c, d, e, g[f + 5], 5, 3593408605); + e = k(e, b, c, d, g[f + 10], 9, 38016083); + d = k(d, e, b, c, g[f + 15], 14, 3634488961); + c = k(c, d, e, b, g[f + 4], 20, 3889429448); + b = k(b, c, d, e, g[f + 9], 5, 568446438); + e = k(e, b, c, d, g[f + 14], 9, 3275163606); + d = k(d, e, b, c, g[f + 3], 14, 4107603335); + c = k(c, d, e, b, g[f + 8], 20, 1163531501); + b = k(b, c, d, e, g[f + 13], 5, 2850285829); + e = k(e, b, c, d, g[f + 2], 9, 4243563512); + d = k(d, e, b, c, g[f + 7], 14, 1735328473); + c = k(c, d, e, b, g[f + 12], 20, 2368359562); + b = l(b, c, d, e, g[f + 5], 4, 4294588738); + e = l(e, b, c, d, g[f + 8], 11, 2272392833); + d = l(d, e, b, c, g[f + 11], 16, 1839030562); + c = l(c, d, e, b, g[f + 14], 23, 4259657740); + b = l(b, c, d, e, g[f + 1], 4, 2763975236); + e = l(e, b, c, d, g[f + 4], 11, 1272893353); + d = l(d, e, b, c, g[f + 7], 16, 4139469664); + c = l(c, d, e, b, g[f + 10], 23, 3200236656); + b = l(b, c, d, e, g[f + 13], 4, 681279174); + e = l(e, b, c, d, g[f + 0], 11, 3936430074); + d = l(d, e, b, c, g[f + 3], 16, 3572445317); + c = l(c, d, e, b, g[f + 6], 23, 76029189); + b = l(b, c, d, e, g[f + 9], 4, 3654602809); + e = l(e, b, c, d, g[f + 12], 11, 3873151461); + d = l(d, e, b, c, g[f + 15], 16, 530742520); + c = l(c, d, e, b, g[f + 2], 23, 3299628645); + b = m(b, c, d, e, g[f + 0], 6, 4096336452); + e = m(e, b, c, d, g[f + 7], 10, 1126891415); + d = m(d, e, b, c, g[f + 14], 15, 2878612391); + c = m(c, d, e, b, g[f + 5], 21, 4237533241); + b = m(b, c, d, e, g[f + 12], 6, 1700485571); + e = m(e, b, c, d, g[f + 3], 10, 2399980690); + d = m(d, e, b, c, g[f + 10], 15, 4293915773); + c = m(c, d, e, b, g[f + 1], 21, 2240044497); + b = m(b, c, d, e, g[f + 8], 6, 1873313359); + e = m(e, b, c, d, g[f + 15], 10, 4264355552); + d = m(d, e, b, c, g[f + 6], 15, 2734768916); + c = m(c, d, e, b, g[f + 13], 21, 1309151649); + b = m(b, c, d, e, g[f + 4], 6, 4149444226); + e = m(e, b, c, d, g[f + 11], 10, 3174756917); + d = m(d, e, b, c, g[f + 2], 15, 718787259); + c = m(c, d, e, b, g[f + 9], 21, 3951481745); + b = i(b, o); + c = i(c, p); + d = i(d, q); + e = i(e, r); + } return (n(b) + n(c) + n(d) + n(e)).toLowerCase(); + }; + + return MD5(this.toId(text)); + } + + HSLToRGB(H, S, L) { + const C = (100 - Math.abs(2 * L - 100)) * S / 100 / 100; + const X = C * (1 - Math.abs((H / 60) % 2 - 1)); + const m = L / 100 - C / 2; + + let R1; + let G1; + let B1; + switch (Math.floor(H / 60)) { + case 1: R1 = X; G1 = C; B1 = 0; break; + case 2: R1 = 0; G1 = C; B1 = X; break; + case 3: R1 = 0; G1 = X; B1 = C; break; + case 4: R1 = X; G1 = 0; B1 = C; break; + case 5: R1 = C; G1 = 0; B1 = X; break; + case 0: default: R1 = C; G1 = X; B1 = 0; break; + } + const R = R1 + m; + const G = G1 + m; + const B = B1 + m; + return {R, G, B}; + } + + toHex(x) { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? `0${hex}` : hex; + } + + hashColor(name, type = 0) { + const hashed = this.hash(this.toId(name)); + const H = parseInt(hashed.substr(4, 4), 16) % 360; // 0 to 360 + const S = parseInt(hashed.substr(0, 4), 16) % 50 + 40; // 40 to 89 + let L = Math.floor(parseInt(hashed.substr(8, 4), 16) % 20 + 30); // 30 to 49 + const {R, G, B} = this.HSLToRGB(H, S, L); + const lum = R * R * R * 0.2126 + G * G * G * 0.7152 + B * B * B * 0.0722; // 0.013 (dark blue) to 0.737 (yellow) + let HLmod = (lum - 0.2) * -150; // -80 (yellow) to 28 (dark blue) + if (HLmod > 18) HLmod = (HLmod - 18) * 2.5; + else if (HLmod < 0) HLmod = (HLmod - 0) / 3; + else HLmod = 0; + // let mod = ';border-right: ' + Math.abs(HLmod) + 'px solid ' + (HLmod > 0 ? 'red' : '#0088FF'); + const Hdist = Math.min(Math.abs(180 - H), Math.abs(240 - H)); + if (Hdist < 15) { + HLmod += (15 - Hdist) / 3; + } + + L += HLmod; + + const r = this.HSLToRGB(H, S, L); + if (type === 1) return `#${this.toHex(r["R"])}${this.toHex(r["G"])}${this.toHex(r["B"])}`; + return `#${this.toHex(r["R"])}${this.toHex(r["G"])}${this.toHex(r["B"])} | hsl(${Math.round(H)}, ${Math.round(S)}, ${L})`; + } } const tools = new Tools(); diff --git a/twitch/app.js b/twitch/app.js new file mode 100644 index 0000000..03ade1b --- /dev/null +++ b/twitch/app.js @@ -0,0 +1,78 @@ +"use strict"; + +let listen = false; + +bot.on("connecting", (async (address, port) => { + console.log(`${Tools.twitchText()}Connecting to ${`${address}:${port}`.cyan}`); +})); + +bot.on("logon", (async () => { + console.log(`${Tools.twitchText()}Logging in...`); +})); + +bot.on("connected", (async (address, port) => { + console.log(`${Tools.twitchText()}Logged in and connected!`); + + global.TwitchCommandHandler = require("./commandHandler.js"); + global.twitchCommandHandler = new TwitchCommandHandler(); + await twitchCommandHandler.init(); + + /*global.TwitchMessageParser = require("./messageParser.js"); + global.twitchMessageParser = new TwitchMessageParser(); + await twitchMessageParser.init();*/ + + // From https://github.com/sirDonovan/Cassius/blob/master/app.js#L46 + let pluginsList; + const plugins = fs.readdirSync(path.resolve(`${__dirname}/plugins`)); + for (let i = 0, len = plugins.length; i < len; i++) { + const fileName = plugins[i]; + if (!fileName.endsWith(".js")) continue; + if (!pluginsList) pluginsList = []; + const file = require(`./plugins/${fileName}`); + if (file.name && !file.disabled) { + global[`twitch-${file.name}`] = file; + if (typeof file.onLoad === "function") file.onLoad(); + } + pluginsList.push(file); + } + + global.TwitchPlugins = pluginsList; + + listen = true; +})); + +bot.on("join", (channel, username, self) => { + if (!self) return; + console.log(`${Tools.twitchText()}Joined channel: ${channel.green}`); +}); + +/*bot.on("raw_message", (messageCloned, message) => { + console.log(message.raw); +});*/ + + +bot.on("message", (async (channel, user, message, self) => { + if (!listen) return; + /*console.log(channel); + console.log(user); + console.log(message); + console.log(self);*/ + if (self) return; + + if (message.startsWith(twitchConfig.commandCharacter) || message.startsWith("!")) { + resolveMessage(channel, user, message, self); + } +})); + +bot.connect(); + + +async function resolveMessage(channel, user, message, self) { + const cmd = Tools.toId(message.slice(1).split(" ", 1)[0]); + let args = message.slice(cmd.length + 1).split(","); + args = args.map(element => element.trim()); + + if (cmd === "help") return twitchCommandHandler.helpCommand(message, channel, user, self); + + twitchCommandHandler.executeCommand(message, channel, user, self); +} diff --git a/twitch/commandHandler.js b/twitch/commandHandler.js new file mode 100644 index 0000000..86fe35a --- /dev/null +++ b/twitch/commandHandler.js @@ -0,0 +1,281 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +const Commands = require(path.resolve(__dirname, "./commands.js")); +const Dex = require("../pokemon-showdown/.sim-dist/dex.js").Dex; +// const utilities = require(path.resolve(__dirname, "./utilities.js")); + +const COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/"); +const DEV_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/dev/"); +const DEX_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/dex/"); +const PRIVATE_COMMANDS_DIRECTORY = path.resolve(__dirname, "./commands/private/"); + +// const databaseDirectory = path.resolve(__dirname, "../../databases"); + +class CommandHandler { + constructor() { + this.commands = []; + } + + async init(isReload) { + console.log(`${Tools.twitchText()}${isReload ? "Rel" : "L"}oading commands...`); + await Promise.all([ + this.loadDirectory(COMMANDS_DIRECTORY, Commands.TwitchCommand, "Bot", isReload), + this.loadDirectory(DEV_COMMANDS_DIRECTORY, Commands.DevCommand, "Dev", isReload), + this.loadDirectory(DEX_COMMANDS_DIRECTORY, Commands.DexCommand, "Dex", isReload), + this.loadDirectory(PRIVATE_COMMANDS_DIRECTORY, Commands.PrivateCommand, "Private", isReload), + ]); + } + + loadDirectory(directory, Command, type, isReload) { + console.log(`${Tools.twitchText()}${isReload ? "Rel" : "L"}oading ${type.cyan} commands...`); + return new Promise((resolve, reject) => { + fs.readdir(directory, (err, files) => { + if (err) { + reject(`Error reading commands directory: ${err}`); + } else if (!files) { + reject(`No files in directory ${directory}`); + } else { + for (let name of files) { + if (name.endsWith(".js")) { + try { + name = name.slice(0, -3); // remove extention + const command = new Command(name, require(`${directory}/${name}.js`)); + this.commands.push(command); + /*fs.readdir(databaseDirectory, (err, dbs) => { + for (let id of dbs) { + if (id.endsWith(".json")) { + id = id.slice(0, -5); // remove extention + if (client.guilds.cache.get(id) !== undefined) { + utilities.populateDb(id, name, type); + } + } + } + });*/ + if (!(isReload)) console.log(`${Tools.twitchText()}${isReload ? "Rel" : "L"}oaded command ${type === "Private" ? (`${name.charAt(0)}*****`).green : name.green}`); + } catch (e) { + console.log(`${Tools.twitchText()}${"CommandHandler loadDirectory() error: ".brightRed}${e} while parsing ${name.yellow}${".js".yellow} in ${directory}`); + console.log(e.stack); + } + } + } + console.log(`${Tools.twitchText()}${type.cyan} commands ${isReload ? "rel" : "l"}oaded!`); + resolve(); + } + }); + }); + } + + get(name, list) { + const commandsList = list ? list : this.commands; + for (const command of commandsList) { + if ([command.name, ...command.aliases].includes(Tools.toId(name))) return command; + } + throw new Error(`commandHandler error: Command "${name}" not found!`); + } + + async executeCommand(message, channel, user, self, time) { + let passDex = Dex; + message = message.substr(1); // Remove command character + + const spaceIndex = message.indexOf(" "); + let args = []; + let cmd = ""; + if (spaceIndex !== -1) { + cmd = message.substr(0, spaceIndex); + args = message.substr(spaceIndex + 1).split(","); + } else { + cmd = message; + } + + const commandsList = this.commands; + for (let i = 0; i < commandsList.length; i++) { + const command = commandsList[i]; + if (command.trigger(cmd)) { + // Permissions checking + if (!isDeveloper(user)) { + if (command.developerOnly) return bot.whisper(user.username, "You need to be a bot developer to use that command!"); + if (command.requiredRank && !hasBadge(user, command.requiredRank)) return bot.whisper(user.username, `You need to be a ${command.requiredRank} or higher to use ${twitchConfig.commandCharacter}${command.name} in ${channel.replace("#", "")}'s channel!'`); + if (command.channels && !command.channels.includes(channel)) return bot.whisper(user.username, `The command ${twitchConfig.commandCharacter}${command.name} is not usable in ${channel.replace("#", "")}'s channel!`); + if (command.noWhisper && user["message-type"] === "whisper") return bot.whisper(user.username, `The command ${twitchConfig.commandCharacter}${command.name} is not usable in whispers!`); + if (command.whisperOnly && user["message-type"] !== "whisper") return bot.whisper(user.username, `The command ${twitchConfig.commandCharacter}${command.name} is only available in whispers!`); + } + + if (command.commandType === "DexCommand") { + for (let i = 0; i < args.length; i++) { + if (["lgpe", "gen7", "gen6", "gen5", "gen4", "gen3", "gen2", "gen1", "usum", "sm", "oras", "xy", "bw2", "bw", "hgss", "dppt", "adv", "rse", "frlg", "gsc", "rby"].includes(Tools.toId(args[i]))) { + switch (Tools.toId(args[i])) { + case "lgpe": + passDex = Dex.mod("lgpe"); + break; + case "gen7": + case "usum": + case "sm": + passDex = Dex.mod("gen7"); + break; + case "gen6": + case "oras": + case "xy": + passDex = Dex.mod("gen6"); + break; + case "gen5": + case "bw2": + case "bw": + passDex = Dex.mod("gen5"); + break; + case "gen4": + case "hgss": + case "dppt": + passDex = Dex.mod("gen4"); + break; + case "gen3": + case "adv": + case "rse": + case "frlg": + passDex = Dex.mod("gen3"); + break; + case "gen2": + case "gsc": + passDex = Dex.mod("gen2"); + break; + case "gen1": + case "rby": + case "rbyg": + passDex = Dex.mod("gen1"); + break; + default: + passDex = Dex.mod("gen7"); + } + args.splice(i, 1); + break; + } + } + } + + const hrStart = process.hrtime(); + console.log(`${Tools.twitchText()}Executing command: ${command.name.cyan}`); + try { + if (command.commandType === "DexCommand") { + await command.execute(args, channel, user, self, passDex); + } else { + await command.execute(args, channel, user, self); + } + } catch (e) { + let stack = e.stack; + stack += "Additional information:\n"; + stack += `Command = ${command.name}\n`; + stack += `Args = ${args}\n`; + stack += `Time = ${new Date(time).toLocaleString()}\n`; + stack += `User = ${user.username}\n`; + stack += `Room = ${channel}`; + console.log(stack); + } + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.twitchText()}Executed command: ${command.name.green} in ${timeString}`); + } + } + } + + helpCommand(message, room, user, time) { + const hrStart = process.hrtime(); + const commandsList = this.commands; + console.log(`${Tools.twitchText()}Executing command: ${"help".cyan}`); + const botCommands = []; + const devCommands = []; + const dexCommands = []; + let sendMsg = []; + + if (!(message.trim().includes(" "))) { + user.say(`List of commands; use \`\`${twitchConfig.commandCharacter}help \`\` for more information:`); + for (let i = 0; i < commandsList.length; i++) { + const command = commandsList[i]; + if (!command.disabled && command.commandType !== "PrivateCommand") { + const cmdText = `${twitchConfig.commandCharacter}${command.name}${command.desc ? ` - ${command.desc}` : ""}`; + switch (command.commandType) { + case "BotCommand": + botCommands.push(cmdText); + break; + case "DevCommand": + devCommands.push(cmdText); + break; + case "DexCommand": + dexCommands.push(cmdText); + break; + } + } + } + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.twitchText()}Executed command: ${"help".green} in ${timeString}`); + if (botCommands.length > 0) { + user.say("**Bot commands**"); + for (const line of botCommands) user.say(line); + } + if (devCommands.length > 0) { + user.say("**Dev Commands**"); + for (const line of devCommands) user.say(line); + } + if (dexCommands.length > 0) { + user.say("**Dex Commands**"); + for (const line of dexCommands) user.say(line); + } + return true; + } + const lookup = Tools.toId(message.split(" ")[1]); + let command; + let matched = false; + console.log(commandsList); + for (let i = 0; i < commandsList.length; i++) { + if (matched) break; + command = commandsList[i]; + if (command.name === lookup || (command.aliases && command.aliases.includes(lookup))) matched = true; + } + + if (!matched) return user.say(`No command "${lookup}" found!`); + + sendMsg = [ + `Help for: ${command.name}`, + `Usage: \`\`${psConfig.commandCharacter}${command.name}${command.usage.length > 0 ? ` ${command.usage}` : ""}\`\``, + `Description: ${command.longDesc}`, + `${command.aliases.length > 0 ? `Aliases: ${command.aliases.join(", ")}` : ""}`, + ]; + if (command.options) { + for (let i = 0; i < command.options.length; i++) { + sendMsg.push(`${command.options[i].toString()}: ${command.options[i].desc}`); + } + } + const hrEnd = process.hrtime(hrStart); + const timeString = hrEnd[0] > 3 ? `${hrEnd[0]}s ${hrEnd[1]}ms`.brightRed : `${hrEnd[0]}s ${hrEnd[1]}ms`.grey; + console.log(`${Tools.twitchText()}Executed command: ${"help".green} in ${timeString}`); + for (const line of sendMsg) user.say(line); + return; + } +} + +module.exports = CommandHandler; + +function isDeveloper(user) { + return twitchConfig.developers.includes(user.username); +} + +function hasBadge(user, rank) { + const userRanks = []; + if (user.badges) { + for (const badge of user.badges) userRanks.push(badge); + } + const groups = { + "vip": 1, + "founder": 2, + "subscriber": 2, + "moderator": 3, + "broadcaster": 4, + }; + let hasRank = false; + for (const group of userRanks) { + if (groups[group] && groups[rank] && groups[group] >= groups[rank]) hasRank = true; + } + return hasRank; +} diff --git a/twitch/commands.js b/twitch/commands.js new file mode 100644 index 0000000..5f42008 --- /dev/null +++ b/twitch/commands.js @@ -0,0 +1,112 @@ +"use strict"; + +class Command { + constructor(name, cmd) { + this.name = name.toLowerCase(); + this.usage = cmd.usage || ""; + this.desc = cmd.desc || "No description."; + this.longDesc = cmd.longDesc || this.desc; + this.aliases = cmd.aliases || []; + this.disabled = cmd.disabled || false; + this.developerOnly = cmd.developerOnly || false; + this.requiredRank = cmd.requiredRank || false; + this.channels = cmd.channels || false; + this.process = cmd.process; + this.noWhisper = cmd.noWhisper || false; + this.whisperOnly = cmd.whisperOnly || false; + this.commandType = "Command"; + + if (Array.isArray(this.longDesc)) { + this.longDesc = this.longDesc.join("\n"); + } + } + + trigger(cmd = "") { + if (this.disabled) { + return false; + } + cmd = cmd.replace(/\s/gi, "").toLowerCase(); + if (cmd === `${this.name}`) { + return true; + } + for (let i = 0; i < this.aliases.length; i++) { + if (cmd === `${this.aliases[i]}`) { + return true; + } + } + return false; + } + + execute(args, channel, user, self) { + return this.process(args, channel, user, self); + } + + toString() { + return `${twitchConfig.commandCharacter}${this.name} ${this.usage}`; + } +} + +class DevCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "DevCommand"; + } + + execute(args, channel, user, self) { + if (this.disabled) { + return; + } + return this.process(args, channel, user, self); + } +} + +class PrivateCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "PrivateCommand"; + } + + execute(args, channel, user, self) { + if (this.disabled) { + return; + } + return this.process(args, channel, user, self); + } +} + +class DexCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "DexCommand"; + } + + execute(args, channel, user, self, dex) { + if (this.disabled) { + return; + } + if (!dex) { + return; + } + return this.process(args, channel, user, self, dex); + } +} + +class TwitchCommand extends Command { + constructor(name, cmd) { + super(name, cmd); + this.commandType = "BotCommand"; + } + + execute(args, channel, user, self) { + if (this.disabled) { + return; + } + return this.process(args, channel, user, self); + } +} + +module.exports.Command = Command; +module.exports.DevCommand = DevCommand; +module.exports.DexCommand = DexCommand; +module.exports.TwitchCommand = TwitchCommand; +module.exports.PrivateCommand = PrivateCommand; diff --git a/twitch/commands/dev/eval.js b/twitch/commands/dev/eval.js new file mode 100644 index 0000000..5d706b6 --- /dev/null +++ b/twitch/commands/dev/eval.js @@ -0,0 +1,19 @@ +"use strict"; + +module.exports = { + desc: "Evaluates arbitrary javascript.", + usage: "", + aliases: ["js"], + developerOnly: true, + async process(args, channel, user, self) { + args = args.join(",").trim(); + let output; + try { + output = eval(args); + output = JSON.stringify(output, null, 2); + } catch (e) { + return bot.whisper(user.username, `Error while evaluating expression: ${e}`); + } + return user["message-type"] === "whisper" ? bot.whisper(user.username, output) : bot.say(channel, output); + }, +}; diff --git a/twitch/commands/dev/hotpatch.js b/twitch/commands/dev/hotpatch.js new file mode 100644 index 0000000..6374346 --- /dev/null +++ b/twitch/commands/dev/hotpatch.js @@ -0,0 +1,57 @@ +"use strict"; + +module.exports = { + desc: "Live reloads modules.", + usage: ", silent", + aliases: ["reload", "rl"], + developerOnly: true, + async process(args, channel, user, self) { + let silent; + if (args[0]) silent = true; + console.log(`${Tools.twitchText()}Hot-patching ${silent ? "(silently)".grey : ""}...`); + console.log(`${Tools.twitchText()}--------------`); + + for (const plugin of TwitchPlugins) { + if (typeof plugin.onEnd === "function") { + console.log(`${Tools.twitchText()}Unloading ${plugin.name.cyan} module...`); + plugin.onEnd(); + } + } + + Tools.uncacheDir("twitch/"); + Tools.uncacheDir("sources/"); + + global.Storage = require("../../../sources/storage.js"); + Storage.importDatabases(); + global.Tools = require("../../../sources/tools.js"); + + // From https://github.com/sirDonovan/Cassius/blob/master/app.js#L46 + let pluginsList; + const plugins = fs.readdirSync(path.resolve(`${__dirname}/../../plugins`)); + for (let i = 0, len = plugins.length; i < len; i++) { + const fileName = plugins[i]; + if (!fileName.endsWith(".js")) continue; + if (!pluginsList) pluginsList = []; + const file = require(`../../plugins/${fileName}`); + if (file.name && !file.disabled) { + global[`twitch-${file.name}`] = file; + if (typeof file.onLoad === "function") file.onLoad(); + } + pluginsList.push(file); + } + + global.TwitchPlugins = pluginsList; + + /*global.DiscordMessageParser = require("../../messageParser.js"); + global.discordMessageParser = new DiscordMessageParser(); + discordMessageParser.init(true);*/ + + global.TwitchCommandHandler = require("../../commandHandler.js"); + global.twitchCommandHandler = new TwitchCommandHandler(); + twitchCommandHandler.init(true); + + if (!silent) { + return user["message-type"] === "whisper" ? bot.whisper(user.username, "Hotpatch completed!") : bot.say(channel, "Hotpatch completed!"); + } + }, +}; diff --git a/twitch/commands/discord.js b/twitch/commands/discord.js new file mode 100644 index 0000000..0553e09 --- /dev/null +++ b/twitch/commands/discord.js @@ -0,0 +1,13 @@ +"use strict"; + +module.exports = { + desc: "Gets/sets a channels discord invite link.", + async process(args, channel, user, self) { + const db = Storage.getDatabase(channel); + if (user.badges && user.badges["broadcaster"] && args[0]) { + db.discord = args.join(","); + Storage.exportDatabase(channel); + } + return bot.say(channel, db.discord ? db.discord : "No discord link set!"); + }, +}; diff --git a/twitch/commands/f.js b/twitch/commands/f.js new file mode 100644 index 0000000..719d4f9 --- /dev/null +++ b/twitch/commands/f.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + desc: "Pays respects.", + async process(args, channel, user, self) { + return user["message-type"] === "whisper" ? bot.whisper(user.username, `${user["display-name"]} paid their respects.`) : bot.say(channel, `${user["display-name"]} paid their respects.`); + }, +}; diff --git a/twitch/commands/id.js b/twitch/commands/id.js new file mode 100644 index 0000000..0ea3a9a --- /dev/null +++ b/twitch/commands/id.js @@ -0,0 +1,13 @@ +"use strict"; + +module.exports = { + desc: "Gets/sets a game ID code.", + async process(args, channel, user, self) { + const db = Storage.getDatabase(channel); + if (user.badges && user.badges["broadcaster"] && args[0]) { + db.id = args.join(","); + Storage.exportDatabase(channel); + } + return bot.say(channel, db.id ? db.id : "No ID set!"); + }, +}; diff --git a/twitch/config-example.json b/twitch/config-example.json new file mode 100644 index 0000000..6fed0c3 --- /dev/null +++ b/twitch/config-example.json @@ -0,0 +1,11 @@ +{ + "identity": { + "username": "yourepicbot", + "password": "oauth:aaaaAAAAaaAaAAaAAAAAAaaaaaAAAa" + }, + "channels": [ + "#yourepicusername", + ], + "commandCharacter": "\\", + "developers": ["yourepicusername"] +}