diff --git a/.gitignore b/.gitignore index 780f313..f47b2d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ todo.txt +*.log *.swp dev.sqlite3 -dev-token +dev-config.json diff --git a/.replit b/.replit new file mode 100644 index 0000000..b7a497d --- /dev/null +++ b/.replit @@ -0,0 +1,2 @@ +language = "nodejs" +run = "node ." diff --git a/COPYING.md b/LICENSE.md similarity index 100% rename from COPYING.md rename to LICENSE.md diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..bda5aaa --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: node . diff --git a/README.md b/README.md index 1daa57d..81d2fee 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ -## Overview -A Discord bot that can assign roles based on message reactions. +# Overview + +AGPL-3.0 Logo + + +A Discord bot that can assign roles based on message reactions.
[You can invite my live instance of the bot to your server with this link]( https://discord.com/oauth2/authorize?client_id=692585944934514738&scope=bot&permissions=335881280 -) +). -## Why this bot? +# Why this bot? Several other popular role-react bots exist, but many of them have some annoying catch. Some have uptime issues, some lock basic functionality behind premium pay walls, and some come with way too many other features that add bloat, requiring @@ -12,15 +17,22 @@ convoluted web APIs for configuration. In most cases the source code also isn't available, so we can't do anything about it. This bot attempts to address these issues. It _only_ does role reacts, and is -configured by typing to it in a Discord channel. Every feature of this bot is -completely free to use, and always will be. It's also open source, so you can -modify it to better suit your needs, or just download it and host your own -instance. Basically, there's no bullshit. +configured using slash commands. Every feature of this bot is completely free to +use, and always will be. It's also open source, so you can modify it to better +suit your needs, or just download it and host your own instance. + +Basically, there's no bullshit. # Usage -You can interact with the bot by mentioning it (denoted here as `@bot`). The bot -will currently only respond to users with the "Administrator" permissions. +You can interact with the bot using slash commands. +The bot will always respond to users with the "Administrator" permission. +Additional roles can be whitelisted to modify the bot config for your guild. + +You write the post people can react to for their roles. The bot will not attempt +to write its own posts for this (but will add its own emojis). + +## Permissions The role automatically created for the bot needs to be ordered above any role you want the bot to be able to assign. That role also needs to have access to the channel with your role-react post, and have have following permissions: @@ -30,102 +42,83 @@ the channel with your role-react post, and have have following permissions: * **Read Message History** - To see posts in the channel before it joined * **Use External Emojis** - To use your custom emojis in role reacts * **Read Text Channels & See Voice Channels** - To see the role-react post -Note that these permissions may be inherited from your `@everyone` settings. -You write the post people can react to for their roles. The bot will not attempt -to write its own posts for this. You can then tell the bot to select that post, -and tell it which roles to map to which emoji reactions on that post. - -**Selecting a post** - The bot tracks this on a per-user basis, so multiple users can -interact with the bot at the same time. -``` -# Command: -@bot select -@bot select -@bot select # Not yet supported! - -# Examples: -@bot select #role-assginment 123456789123456789 -@bot select 123456789123456789 1234556789123456789 -@bot select https://discordapp.com/channels/123456789123456789/123456789123456789/123456789123456789 -``` - -**Adding a role to the post** - The bot will add its own reaction to the selected -post with the given emoji. -``` -# Command: -@bot role-add -@bot role-add - -# Examples: -@bot role-add 🦊 @test-role -@bot role-add 🦊 1234556789123456789 -``` - -**Removing a react-role from a post** - The bot will remove all reactions of -this emoji from the selected post, without removing the associated role from any -user who reacted to the post. -``` -# Command -@bot role-remove - -# Examples: -@bot role-remove 🦊 -``` - -**Removing all react-roles from a post** - The bot will remove all reactions -from the selected post, without removing any of the associated roles from the -members who reacted to it. This is mostly useful to work around a limitation -with Discord's API, since it treats admins removing reacts the same was as users -removing reacts. -``` -# Command -@bot role-remove-all -``` - -**Adding roles that can configure the bot** - By default, the bot will only -listen to users who have the administrator permission. This command allows you -to add additional roles that are allowed to configure the bot. -``` -# Command -@bot perm-add -``` - -**Removing roles that can configure the bot** - Removes a role from being -allowed to configure the bot. The bot will always listen to users with the -administrator permissions. This cannot currently be disabled. -``` -# Command -@bot perm-remove -``` - -**Add mutually exclusive roles** - Makes two roles mutually exclusive. If a user -tried to add two roles that are mutually exclusive, the bot will automatically -remove the first one they had. -``` -# Command -@bot mutex-add -``` - -**Remove mutually exclusive roles** - Removes the mutually exclusive constraint -from two roles. -``` -# Command -@bot mutex-remove -``` - -**Printing command usage info** - If you'd rather the bot tell you how to use -it, instead of looking at this page, you can use this command. -``` -# Command -@bot help -``` - -You can also print the bot's description, version number, and link to the source -code. This command is available to all users. -``` -@bot info -``` +**Note:** These permissions may be inherited from your `@everyone` settings. + +## Selecting a Message +You need to select a message to add react roles to it. Right click on a message +and use Discord's fancy context menu to select it. This is tracked per-user, so +multiple users can interact with the bot at the same time. + +Due to current Discord limitations, context menus are not available on mobile +devices (sorry). See below for an alternative. + +![](docs/select.png) + +## Selecting a Message on Mobile +If you are on mobile, you might not have access to context menus. Don't worry, +there's a workaround for you. Long-press the message, select +"Copy Message Link", then use `/select-message-mobile` with the message URL +instead. + +| This is the Message | Long press and Share | Tap Copy | +|---------------------------|---------------------------|---------------------------| +|![](docs/mobileselect1.png)|![](docs/mobileselect2.png)|![](docs/mobileselect3.png)| + +![](docs/mobileselect4.png) + +## Adding a Role to the Message +Use the `/role add` slash command to add a role to the message. + +![](docs/roleadd.png) + +You can also remove a role from the message using `/role remove`, or remove +all roles from the message using `/role remove-all`. This will remove all +reactions from the post, but **will not** unassign any roles. + +## Making Two Roles Mutually Exclusive +Use the `/mutex add` slash command to make two (or more) roles mutually +exclusive. If a user tries to add two roles that are mutually exclusive, the bot +will automatically remove the first one they had. You can have multiple mutually +exclusive groups. + +This setting is **server-wide**. If roles A, B, and C are all mutually +exclusive, when a user with roles B and C tries to assign role A, the bot will +automatically remove roles B and C from them **even if those roles were not +assigned by the bot in the first place**. + +Due to current Discord limitations, you can only add two roles per-command, so +you'll need to run the command several times to make more than two roles +mutually exclusive (once again, sorry). + +![](docs/mutexadd.png) + +Use `/mutex remove` to remove to mutually exclusive restriction on two roles. + +## Adding Roles That Can Configure The Bot +By default, the bot will only listen to users who have the "Administrator" +permission. You can use `/permission add` to add additional roles that are +allowed to configure the bot in your guild. + +![](docs/permadd.png) + +Use `/permission remove` to disallow a role from configuring the bot.
+**Note:** A user can remove their own permission to configure the bot if they +are not an administrator! + +## Delete All Configuration In Server +You can use `/reset-everything` to remove *all* configuration the bot has for +the guild the command was sent from. The bot does this automatically when it is +kicked from a guild. + +This does not clear any server-side logs for the guild. + +## Printing Bot Info +You can use `/info` to print the bot's description, version number, link to the +source code, and some fun stats. This command is available to all users and +posts **non-ephemeral** replies (i.e. visible to everybody). + +![](docs/info.png) ## Rate Limits If the bot is taking a few moments to respond to reactions, it is likely hitting @@ -133,47 +126,21 @@ Discord's strict rate limit. This happens most often with mutually exclusive roles, since the bot needs to make several requests to make them work. The bot is registering the actions. Give it a few seconds to catch up. +## @everyone +Discord implements `@everyone` as a role under the hood. This means you can set +up react roles for `@everyone` like you would for any other role. It's silly, it +won't do anything, but you *could* do it... + ## Hosting your own instance -This bot is built on [discord.js](https://discord.js.org/#/) v12, so you'll need -Node.js 12.0.0 (or newer) installed. You will also need your own Discord bot -account. - -The `resources` directory has a service file that can be used with Linux distros -with systemd. If you're installing this on some other operating system, you're -on your own. - -### Running as a service -The provided service file expects to find the bot code at -`/srv/discord/ReactionRoleBot/`, and will want to create the sqlite database at -`/srv/discord/rolebot.sqlite`. The easiest way to do this is to create a -`/srv/discord` directory, and `chown` it so it belongs to the user running the -bot. - -The following will prepare the bot to run. Run this from `/srv/discord`: -``` -git clone https://github.com/Mimickal/ReactionRoleBot.git -cd ReactionRoleBot -npm install -NODE_ENV=prod npm run knex migrate:latest -``` - -Create a file `/etc/discord/ReactionRoleBot/token` and paste your bot token in, -in plain text. - -Install `reactionrolebot.service` into `/etc/systemd/system/`. - -Now you should be able to run `systemctl restart reactionrolebot.service` to -start your bot. - -### Running locally (in dev-mode) -Run this wherever you want: -``` -git clone https://github.com/Mimickal/ReactionRoleBot.git -cd ReactionRoleBot -npm install -npm run knex migrate:latest -``` - -Create a file containing your bot token in plain text. - -Run this to start the bot: `node main.js path/to/your/token` +[See the guide here](docs/hosting.md). + +If you are upgrading from version 1.x, [see the migration guide]( +docs/migrate.md). + +## License +Copyright 2020 [Mimickal](https://github.com/Mimickal) + +This code is licensed under the +[AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0-standalone.html) license. + +Basically, any modifications to this code must be made open source. diff --git a/cache.js b/cache.js deleted file mode 100644 index 481814b..0000000 --- a/cache.js +++ /dev/null @@ -1,128 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -const database = require('./database'); - -// The live cache of selected messages for each user. -const selectedMessages = new Map(); - -/** - * Selects a message to use for each subsequent command the user enters. - */ -function selectMessage(user_id, message) { - selectedMessages.set(user_id, message); - // TODO clear selected cache after some time -} - -/** - * Gets the message the given user currently has selected. - * - * Throws an exception if the user has no message selected. - */ -function getSelectedMessage(user_id) { - let message = selectedMessages.get(user_id); - if (!message) { - throw new Error('No message selected!'); - } - return message; -} - -/** - * Clears the selected message for the given user. - */ -function clearSelectedMessage(user_id) { - selectedMessages.delete(user_id); -} - -/** - * Add an emoji-role association to the message the user has selected. - * If the emoji was already associated with another role on this message, the - * original mapping will be overwritten. - * - * Throws an exception if the user has no message selected. - */ -async function addEmojiRole(user_id, emoji_id, role_id) { - let message = await getSelectedMessage(user_id); - - return database.addRoleReact({ - guild_id: message.guild.id, - message_id: message.id, - emoji_id: emoji_id, - role_id: role_id - }); -} - -/** - * Remove an emoji-role association from the message the user has selected. - * - * Throws an exception: - * - If the user has no message selected. - * - If the message did not have a mapping for the given emoji. - */ -async function removeEmojiRole(user_id, emoji_id) { - let message = getSelectedMessage(user_id); - - let args = { - message_id: message.id, - emoji_id: emoji_id - }; - - let role_id = await database.getRoleReact(args); - if (role_id) { - return database.removeRoleReact(args); - } else { - throw new Error('No role mapping found'); - } -} - -/** - * Removes all emoji-role associations from the message the user has selected. - * - * Throws an exception: - * - If the user has no message selected. - * - If the message did not have any emojis mappings on it. - */ -async function removeAllEmojiRoles(user_id) { - let message = getSelectedMessage(user_id); - - let rows = await database.removeAllRoleReacts(message.id); - if (rows === 0) { - throw new Error('No role mapping found'); - } - - return message; -} - -/** - * Gets the role mapped to the given emoji on the given message, or null if - * there's no role associated with it (or if the message is unknown). - */ -function getReactRole(message_id, emoji_id) { - return database.getRoleReact({ - message_id: message_id, - emoji_id: emoji_id - }); -} - -module.exports = { - selectMessage, - getSelectedMessage, - clearSelectedMessage, - addEmojiRole, - removeEmojiRole, - removeAllEmojiRoles, - getReactRole -}; diff --git a/database.js b/database.js deleted file mode 100644 index cbe12f1..0000000 --- a/database.js +++ /dev/null @@ -1,276 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -const knexfile = require('./knexfile'); -const knex = require('knex')(knexfile[process.env.NODE_ENV || 'development']); -const lodash = require('lodash/object'); - -const META = 'meta'; -const MUTEX = 'mutex'; -const PERMS = 'perms'; -const REACTS = 'reacts'; -const DISCORD_ID_LENGTH = { - MIN: 17, - MAX: 19 -}; - -/** - * Adds an emoji->role mapping for the given message. If the emoji is already - * mapped to a role on this message, that mapping is replaced. - * - * This is essentially an upsert, but "upsert" is a stupid word, so "add" it is. - */ -function addRoleReact(args) { - // TODO sanity check values - let fields = lodash.pick(args, [ - 'guild_id', 'message_id', 'emoji_id', 'role_id' - ]); - - return knex(REACTS) - .insert(fields) - .catch(err => { - if (err.message.includes('UNIQUE constraint failed')) { - return knex(REACTS) - .where(lodash.pick(fields, ['message_id', 'emoji_id'])) - .update({ role_id: fields.role_id }); - } else { - throw err; - } - }); -} - -/** - * Removes an emoji->role mapping for the given message. - */ -function removeRoleReact(args) { - // TODO sanity check values - let fields = lodash.pick(args, ['message_id', 'emoji_id']); - - return knex(REACTS).where(fields).del(); -} - -/** - * Removes all emoji->role mappings for the given message. - */ -function removeAllRoleReacts(message_id) { - return knex(REACTS).where('message_id', message_id).del(); -} - -/** - * Returns the role for the given emoji on the given message, or null if there - * is no role associated with the emoji on the message. - */ -function getRoleReact(args) { - // TODO sanity check values - let fields = lodash.pick(args, ['message_id', 'emoji_id']); - - return knex(REACTS) - .first('role_id') - .where(fields) - .then(result => result ? result.role_id : null); -} - -/** - * Returns the emoji->role mapping for the given message as a Map object, or - * null if the given message has no react roles set up. - */ -function getRoleReactMap(message_id) { - return knex(REACTS) - .select(['emoji_id', 'role_id']) - .where('message_id', message_id) - .then(pairArray => { - let mapping = pairArray.reduce( - (map, pair) => map.set(pair.emoji_id, pair.role_id), - new Map() - ); - - return mapping.size > 0 ? mapping : null; - }); -} - -/** - * Deletes all the data stored for the given guild. - */ -function clearGuildInfo(guild_id) { - return Promise.all([REACTS, PERMS, MUTEX].map(table => - knex(table).where('guild_id', guild_id).del() - )); -} - -/** - * Increments the meta table's role assignment counter. - */ -function incrementAssignCounter(num) { - return knex(META).increment('assignments', num || 1); -} - -/** - * Returns the following object of meta stats about the bot: - * - guilds: - * - roles: - * - assignments: - */ -function getMetaStats() { - return Promise.all([ - knex(REACTS).countDistinct('guild_id as count').first(), - knex(REACTS).count().first(), - knex(META).select('assignments').first() - ]).then(([res1, res2, res3]) => { - return { - guilds: res1['count'], - roles: res2['count(*)'], - assignments: res3.assignments - }; - }); -} - -/** - * Adds a new role that's allowed to configure this bot for the given guild. - */ -function addAllowedRole(args) { - // TODO sanity check values - let fields = lodash.pick(args, ['guild_id', 'role_id']); - - return knex(PERMS).insert(fields); -} - -/** - * Removes a role from being allowed to configure this bot for the given guild. - */ -function removeAllowedRole(args) { - // TODO sanity check values - let fields = lodash.pick(args, ['guild_id', 'role_id']); - - return knex(PERMS).where(fields).del(); -} - -/** - * Returns the list of roles that can configure this bot for the given guild. - */ -function getAllowedRoles(guild_id) { - return knex(PERMS) - .select('role_id') - .where({ guild_id: guild_id }) - .then(roleArray => roleArray.map(elem => elem.role_id)); -} - -/** - * Creates a mutually exclusive rule for two roles in the given guild. - * role_id_1 and role_id_2 are interchangable, so if there's already a record - * for roleA and roleB, attempting to add a record for roleB and roleA will - * throw a unique constraint violation exception. - */ -function addMutexRole(args) { - // TODO sanity check values - let fields = lodash.pick(args, ['guild_id', 'role_id_1', 'role_id_2']); - - // Need to try role 1 and role 2 in reverse order too - let flipped = lodash.pick(args, ['guild_id']); - flipped.role_id_1 = fields.role_id_2; - flipped.role_id_2 = fields.role_id_1; - - return knex(MUTEX) - .first() - .where(fields) - .then(record => { - // If record exists, insert it again to cause a unique constraint - // exception. If not, try to insert the fields in reverse order. - let version = record ? fields : flipped; - return knex(MUTEX).insert(version); - }); -} - -/** - * Removes the mutually exclusive rule for the two roles in the given guild. - * role_id_1 and role_id_2 are interchangable here the same way they are in - * addMutexRole. - */ -function removeMutexRole(args) { - // TODO sanity check values - let fields = lodash.pick(args, ['guild_id', 'role_id_1', 'role_id_2']); - let flipped = lodash.pick(args, ['guild_id']); - flipped.role_id_1 = fields.role_id_2; - flipped.role_id_2 = fields.role_id_1; - - // We can just try to delete with roles in both orders. - return Promise.all([ - knex(MUTEX).where(fields).del(), - knex(MUTEX).where(flipped).del() - ]).then(([count1, count2]) => ((count1 || 0) + (count2 || 0))); -} - -/** - * Returns the list of roles that are mutually exclusive with the given role, - * for the given guild. If no roles are mutually exclusive, an empty array is - * returned. - */ -function getMutexRoles(args) { - // TODO sanity check values - let fields = lodash.pick(args, ['guild_id', 'role_id']) - - // Roles could be added in either order, so fetch with both orders and - // combine the results. - return Promise.all([ - knex(MUTEX).select('role_id_1').where({ - guild_id: fields.guild_id, - role_id_2: fields.role_id - }), - knex(MUTEX).select('role_id_2').where({ - guild_id: fields.guild_id, - role_id_1: fields.role_id - }) - ]).then(([res1, res2]) => [ - ...res1.map(row => row.role_id_1), - ...res2.map(row => row.role_id_2) - ]); -} - -/** - * Takes a list of roles and returns the list of emojis associated with them. - * This is mostly so we can remove reacts in bulk. - * XXX: It might make sense to return this as key-value pairs in the future, - * instead of just an array. - */ -function getMutexEmojis(roles) { - return knex(REACTS) - .select('emoji_id') - .whereIn('role_id', roles) - .then(res => res.map(elem => elem.emoji_id)); -} - -module.exports = { - DISCORD_ID_LENGTH, - META, - MUTEX, - PERMS, - REACTS, - addRoleReact, - removeRoleReact, - removeAllRoleReacts, - getRoleReact, - getRoleReactMap, - clearGuildInfo, - incrementAssignCounter, - getMetaStats, - addAllowedRole, - removeAllowedRole, - getAllowedRoles, - addMutexRole, - removeMutexRole, - getMutexRoles, - getMutexEmojis -}; - diff --git a/docs/hosting.md b/docs/hosting.md new file mode 100644 index 0000000..42ca24a --- /dev/null +++ b/docs/hosting.md @@ -0,0 +1,74 @@ +# Hosting your own instance + +This bot is built on [discord.js](https://discord.js.org/#/) v13, so you'll need +Node.js 16.6.0 (or newer) installed. You will also need your own Discord bot +account. If your platform does not have Node.js 16.6.0, consider using something +like https://github.com/nvm-sh/nvm. + +This guide assumes you're hosting on a Linux distro with `systemd`. The bot will +work on other platforms, but you're on your own figuring that out. + +## Running as a user (in dev-mode) +Quick and easy. Also (mostly) platform-independent! + +Create a file `config.json` and paste in the following (obviously fill in the +blanks with your bot's info): +```json +{ + "token": "", + "app_id": "" +} +``` + +Install dependencies, register Discord slash commands, and set up the database +for your bot: +``` +git clone https://github.com/Mimickal/ReactionRoleBot.git +cd ReactionRoleBot +npm ci +npm run register path/to/your/config.json +npm run knex migrate:latest +``` + +Start the bot: +``` +npm start path/to/your/config.json +``` + +## Running as a service +A little more effort to set up, but better for long-term use. + +The provided service file expects to find the bot code at +`/srv/discord/ReactionRoleBot/`, and will want to create the sqlite database at +`/srv/discord/rolebot.sqlite`. The easiest way to do this is to create a +`/srv/discord` directory, and `chown` it so it belongs to the user running the +bot. + +Create a file `/etc/discord/ReactionRoleBot/config.json` and paste in the +following (obviously fill in the blanks with your bot's info): +```json +{ + "token": "", + "app_id": "" +} +``` + +The following will prepare the bot to run by installing dependencies, +registering slash commands for your Discord bot account, and setting up the +bot's database. Run this from `/srv/discord`: +``` +git clone https://github.com/Mimickal/ReactionRoleBot.git +cd ReactionRoleBot +npm ci +npm run register /etc/discord/ReactionRoleBot/config.json +NODE_ENV=prod npm run knex migrate:latest +``` + +Add your user to `reactionrolebot.service`, then install it into +`/etc/systemd/system/` (just copy the file into that directory). This service +file depends on the above directories, so if you want to change them, you'll +also need to edit those fields. If you are using `nvm`, you may need to tweak +the service file a bit (see comments in provided service file). + +Now you should be able to run `systemctl restart reactionrolebot.service` to +start your bot. diff --git a/docs/info.png b/docs/info.png new file mode 100644 index 0000000..bfecd29 Binary files /dev/null and b/docs/info.png differ diff --git a/docs/migrate.md b/docs/migrate.md new file mode 100644 index 0000000..fa1495a --- /dev/null +++ b/docs/migrate.md @@ -0,0 +1,31 @@ +# Migrating your instance to 2.x + +This guide is for people who were running their own 1.x instance of the bot. + +This bot now runs Discord.js v13, which requires Node.js 16.6.0. On some +platforms (older distros, cloud hosting, etc...) this may be problematic. +Something like https://github.com/nvm-sh/nvm can make the transition easier. + +1. Get the updated bot code (either `git pull origin master` if you cloned with + git (which you should) or download `master` as a zip). +1. Make a copy of your old database + - **Running as a service**: probably `/srv/discord/rolebot.sqlite3` + - **Running in dev-mode**: `dev.sqlite3` +1. Replace bot token file with a `config.json` file that looks like this: + ```json + { + "token": "", + "app_id": "" + } + ``` + - **Running as a service**: Replace `/etc/discord/ReactionRoleBot/token` + with `/etc/discord/ReactionRoleBot/config.json` + - **Running in dev-mode**: Some local token file +1. Install updated dependencies: `npm ci` +1. Register slash commands: `npm run register path/to/your/config.json` +1. Update your database: `npm run knex migrate:latest` +1. Start the bot + - **Running as a service**: update `reactionrolebot.service` ([see reference + implementation](../resources/reactionrolebot.service)). Restart service + `systemctl restart reactionrolebot.service`. + - **Running in dev-mode**: `npm start path/to/your/config.json` diff --git a/docs/mobileselect1.png b/docs/mobileselect1.png new file mode 100644 index 0000000..ba0df43 Binary files /dev/null and b/docs/mobileselect1.png differ diff --git a/docs/mobileselect2.png b/docs/mobileselect2.png new file mode 100644 index 0000000..b2de6e1 Binary files /dev/null and b/docs/mobileselect2.png differ diff --git a/docs/mobileselect3.png b/docs/mobileselect3.png new file mode 100644 index 0000000..0bc917f Binary files /dev/null and b/docs/mobileselect3.png differ diff --git a/docs/mobileselect4.png b/docs/mobileselect4.png new file mode 100644 index 0000000..285b73e Binary files /dev/null and b/docs/mobileselect4.png differ diff --git a/docs/mutexadd.png b/docs/mutexadd.png new file mode 100644 index 0000000..f680399 Binary files /dev/null and b/docs/mutexadd.png differ diff --git a/docs/permadd.png b/docs/permadd.png new file mode 100644 index 0000000..de2c3fc Binary files /dev/null and b/docs/permadd.png differ diff --git a/docs/roleadd.png b/docs/roleadd.png new file mode 100644 index 0000000..74f0be5 Binary files /dev/null and b/docs/roleadd.png differ diff --git a/docs/select.png b/docs/select.png new file mode 100644 index 0000000..b79d09b Binary files /dev/null and b/docs/select.png differ diff --git a/knexfile.js b/knexfile.js deleted file mode 100644 index 397ccd2..0000000 --- a/knexfile.js +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -module.exports = { - development: { - client: 'sqlite3', - useNullAsDefault: true, - connection: { - filename: './dev.sqlite3' - } - }, - - testing: { - client: 'sqlite3', - useNullAsDefault: true, - connection: { - filename: ':memory:' - } - }, - - prod: { - client: 'sqlite3', - useNullAsDefault: true, - connection: { - filename: '/srv/discord/rolebot.sqlite3' - } - } -}; - diff --git a/main.js b/main.js deleted file mode 100644 index 4248e0a..0000000 --- a/main.js +++ /dev/null @@ -1,844 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -const fs = require('fs'); - -const Discord = require('discord.js'); - -const cache = require('./cache'); -const database = require('./database'); - -const info = require('./package.json'); - -// Everything operates on IDs, so we can safely rely on partials. -// This causes reaction events to fire for uncached messages. -const client = new Discord.Client({ - partials: [ - Discord.Constants.PartialTypes.MESSAGE, - Discord.Constants.PartialTypes.CHANNEL, - Discord.Constants.PartialTypes.REACTION - ] -}); -const token_file = process.argv[2] || '/etc/discord/ReactionRoleBot/token'; -const token = fs.readFileSync(token_file).toString().trim(); - -// Map of command names to handling functions. Doubles as a validator. -const COMMANDS = new Map(); -cmdDef(selectMessage, - 'select', ' ', - `Selects the message to perform actions on. This is per-user, so multiple - people can be setting up roles on different messages (or the same one).` -); -cmdDef(setupReactRole, - 'role-add', ' ', - `Creates an emoji-role on the selected message. The bot will automatically - react to the message with this emoji.` -); -cmdDef(removeReactRole, - 'role-remove', '', - `Removes the emoji-role from the message. The bot will remove all reactions - of this emoji from the message.` -); -cmdDef(removeAllReacts, - 'role-remove-all', '', - `Removes all emoji-roles and reactions from the message. This will **not** - remove existing roles from users.` -); -cmdDef(addPermissionRole, - 'perm-add', '', - `Adds a role that is allowed to configure this bot in this server. Note that - this role will be allowed to add more roles with this same permission.` -); -cmdDef(removePermissionRole, - 'perm-remove', '', - `Removes a role from being allowed to configure this bot in this server. - Note that allowed roles can remove their own ability to configure this bot, - and will then be unable to make further changes until an administrator - re-adds their permission.` -); -cmdDef(addMutexRoles, - 'mutex-add', ' ', - `Makes two roles mutually exclusive. If a user attempts to add two mutually - exclusive roles, they will lose the first one they had.` -); -cmdDef(removeMutexRoles, - 'mutex-remove', ' ', - `Removes the mutually exclusive restriction on two roles.` -); -cmdDef(sayInfo, - 'info', '', - 'Prints description, version, and link to source code for the bot' -); -cmdDef(sayHelp, - 'help', '', - 'Prints this help text' -); - - -const Events = Discord.Constants.Events; -client.on(Events.CLIENT_READY, onReady); -client.on(Events.GUILD_CREATE, onGuildJoin); -client.on(Events.GUILD_DELETE, onGuildLeave); -client.on(Events.MESSAGE_CREATE, onMessage); -client.on(Events.MESSAGE_REACTION_ADD, onReactionAdd); -client.on(Events.MESSAGE_REACTION_REMOVE, onReactionRemove); - - -client.login(token).catch(err => { - logError(err); - process.exit(1); -}); - -/** - * Event handler for when the bot is logged in. - */ -function onReady() { - console.log(`Logged in as ${client.user.tag}`); - - // No idea why Discord.js does stuff like this... - // https://github.com/discordjs/discord.js/blob/master/src/util/Constants.js#L431 - const LISTENING = 2; - client.user.setPresence({ - activity: { - name: `'help' for commands. Running version ${info.version}`, - type: Discord.Constants.ActivityTypes[LISTENING] - } - }).catch(logError); -} - -/** - * Event handler for when the bot joins a new guild. - */ -function onGuildJoin(guild) { - let info = unindent(`Hi there! My role needs to be ordered above any - role you would like me to assign. You're getting this message - because you are the server owner, but anybody with Administrator - permissions or an allowed role can configure me.`); - - guild.members.fetch(client.user.id) - .then(clientMember => { - const Perms = Discord.Permissions.FLAGS; - const requiredPermMap = { - [Perms.ADD_REACTIONS]: 'Add Reactions', - [Perms.MANAGE_MESSAGES]: 'Manage Messages', - [Perms.MANAGE_ROLES]: 'Manage Roles', - [Perms.READ_MESSAGE_HISTORY]: 'Read Message History', - [Perms.USE_EXTERNAL_EMOJIS]: 'Use External Emojis', - [Perms.VIEW_CHANNEL]: 'Read Text Channels & See Voice Channels' - }; - - // This bot probably shouldn't be given the admin permission, but if - // we have it then the other ones don't matter. - // Also, these permissions can also be inherited from the server's - // @everyone permissions. - let missingPermNames = Object.entries(requiredPermMap) - .filter(([perm, name]) => !clientMember.hasPermission( - parseInt(perm), - { checkAdmin: true } - )) - .map(([perm, name]) => name); - - if (missingPermNames.length > 0) { - info += '\n\n' + unindent(`Also, I am missing the following - permissions. Without them, I probably won't work right:`) + - '\n' + missingPermNames.join('\n'); - } - - return guild.members.fetch(guild.ownerID); - }) - .then(owner => owner.createDM()) - .then(dmChannel => dmChannel.send(info)) - .catch(logError); -} - -/** - * Event handler for when the bot leaves (or is kicked from) a guild. - */ -function onGuildLeave(guild) { - database.clearGuildInfo(guild.id) - .catch(logError); -} - -/** - * Event handler for getting a new message. - * Parses and delegates any role bot command. - */ -function onMessage(msg) { - // Ignore DMs - if (msg.channel instanceof Discord.DMChannel) { - return; - } - - // Ignore anything where we're not even mentioned - if (!msg.mentions.has(client.user)) { - return; - } - - let msgParts = msg.content.split(/\s+/); - - // Only pay attention to messages where we're mentioned first. - let mentionUserId = extractId(msgParts.shift()); - if (mentionUserId !== client.user.id) { - return; - } - - let cmdName = msgParts.shift(); - - // Only pay attention to messages that are known commands. - if (!COMMANDS.has(cmdName)) { - logError('Possible unrecognized command: ' + msg.content); - return; - } - - userHasPermission(msg, cmdName) - .then(hasPerm => { - if (hasPerm) { - COMMANDS.get(cmdName).get('handler')(msg, msgParts); - } else { - msg.reply("You don't have permission to use that command"); - } - }) - .catch(logError); -} - -/** - * Selects a message to associate with any subsequent role commands. - * Previously selected message is cleared if the user gives bad input for this. - */ -function selectMessage(msg, parts) { - // TODO support selection by URL - - let maybeChannelId = parts.shift(); - let maybeMessageId = parts.shift(); - - let channelId = extractId(maybeChannelId); - let messageId = extractId(maybeMessageId); - - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - else if (!maybeChannelId) issue = 'Missing channel!'; - else if (!maybeMessageId) issue = 'Missing message_id!'; - else if (!channelId) issue = `Invalid channel_id \`${maybeChannelId}\`!`; - else if (!messageId) issue = `Invalid message_id \`${maybeMessageId}\`!`; - - if (issue) { - msg.reply(issue + usage('select')); - cache.clearSelectedMessage(msg.author.id); - return; - } - - client.channels.fetch(channelId) - .then(channel => channel.messages.fetch(messageId)) - .then(message => { - cache.selectMessage(msg.author.id, message); - - return msg.reply( - `selected message with ID \`${message.id}\` ` + - `in channel <#${channelId}>. Link: ${message.url}` - ); - }) - .catch(err => { - // The user is trying to select a new message, so at least clear - // their old selection. Principle of least surprise, and all that... - cache.clearSelectedMessage(msg.author.id); - - let errMsg; - if (err.message === 'Unknown Channel') { - errMsg = "I can't find a channel in this server with ID " - + `\`${channelId}\`.`; - } - else if (err.message === 'Unknown Message') { - errMsg = `I can't find a message with ID \`${messageId}\` ` - + `in channel <#${channelId}>.`; - } - else { - errMsg = `I got an error I don't recognize:\n\`${err.message}\``; - logError(err, 'For message', msg.content); - } - - errMsg += usage('select'); - - msg.reply(errMsg); - }); -} - -/** - * Associate an emoji reaction with a role for the currently selected message. - */ -function setupReactRole(msg, parts) { - // TODO do we want to warn when two emojis map to the same role? - - let rawEmoji = parts.shift(); // Needed to print emoji in command response - let maybeRole = parts.shift(); - - let emoji = extractEmoji(rawEmoji); - let roleId = extractId(maybeRole); - - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - else if (!emoji) issue = 'Missing emoji!'; - else if (!maybeRole) issue = 'Missing role!'; - else if (!roleId) issue = `Invalid role \`${maybeRole}\`!`; - - if (issue) { - msg.reply(issue + usage('role-add')); - return; - } - - let userId = msg.author.id; - - msg.guild.roles.fetch(roleId) - .then(role => { - if (!role) { - throw new Error('Invalid Role'); - } - - return cache.addEmojiRole(userId, emoji, roleId); - }) - .then(() => cache.getSelectedMessage(userId)) - .then(selectedMessage => selectedMessage.react(emoji)) - .then(reaction => msg.reply( - `mapped ${rawEmoji} to <@&${roleId}> on message \`${reaction.message.id}\`` - )) - .catch(err => { - if (err.message === 'No message selected!') { - msg.reply('You need to select a message first!'); - } - else if (err.message === 'Invalid Role') { - msg.reply(`I can't find a role with ID \`${roleId}\``); - } - else if (err.message === 'Unknown Emoji') { - msg.reply( - `I can't find an emoji with ID \`${emoji}\`` - + usage('role-add') - ); - } - else if (err.message === 'Missing Permissions') { - msg.reply("I don't have permission to react to the selected message"); - } - else { - msg.reply(`I got an error I don't recognize:\n\`${err.message}\``); - logError(err, 'For message', msg.content); - } - }); -} - -/** - * Removes an emoji reaction role association from the currently selected - * message. - */ -function removeReactRole(msg, parts) { - let rawEmoji = parts.shift(); - let emoji = extractEmoji(rawEmoji); - - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - else if (!emoji) issue = 'Missing emoji!'; - - if (issue) { - msg.reply(issue + usage('role-remove')); - return; - } - - let userId = msg.author.id; - - Promise.resolve() // Hack to pass error from getSelectedMessage to .catch - .then(() => cache.getSelectedMessage(userId)) - .then(selectedMessage => { - let emojiReacts = selectedMessage.reactions.cache.get(emoji); - - return Promise.all([ - emojiReacts ? emojiReacts.remove() : Promise.resolve(), - cache.removeEmojiRole(userId, emoji) - ]) - .then(() => msg.reply( - `removed ${rawEmoji} role from message \`${selectedMessage.id}\`` - )); - }) - .catch(err => { - if (err.message === 'No message selected!') { - msg.reply('You need to select a message first!'); - } - else if (err.message === 'No role mapping found') { - msg.reply( - `Selected message does not have ${rawEmoji} reaction.\n` + - 'If that displayed as a raw ID instead of an emoji, you ' + - 'might be using the wrong ID.' - ); - } - else if (err.message === 'Missing Permissions') { - msg.reply("I don't have permission to modify the selected message"); - } - else { - msg.reply(`I got an error I don't recognize:\n\`${err.message}\``); - logError(err, 'For message', msg.content); - } - }); -} - -/** - * Removes all emoji-role associations from the currently selected message. - */ -function removeAllReacts(msg, parts) { - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - - if (issue) { - msg.reply(issue + usage('role-remove-all')); - return; - } - - let userId = msg.author.id; - - Promise.resolve() // Hack to pass all errors to .catch - .then(() => cache.getSelectedMessage(userId)) - .then(selectedMessage => selectedMessage.reactions.removeAll()) - .then(() => cache.removeAllEmojiRoles(userId)) - .then(selectedMessage => msg.reply( - `removed all roles from message \`${selectedMessage.id}\`` - )) - .catch(err => { - if (err.message === 'No role mapping found') { - msg.reply('Selected message does not have any role reactions.'); - } - else if (err.message === 'No message selected!') { - msg.reply('You need to select a message first!'); - } - else if (err.message === 'Missing Permissions') { - msg.reply("I don't have permission to modify the selected message"); - } - else { - msg.reply(`I got an error I don't recognize:\n\`${err.message}\``); - logError(err, 'For message', msg.content); - } - }); -} - -/** - * Adds a role that is allowed to configure this bot's settings for a guild. - */ -function addPermissionRole(msg, parts) { - let maybeRole = parts.shift(); - let roleId = extractId(maybeRole); - - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - else if (!maybeRole) issue = 'Missing role!'; - else if (!roleId) issue = `Invalid role \`${maybeRole}\`!`; - - if (issue) { - msg.reply(issue + usage('perm-add')); - return; - } - - msg.guild.roles.fetch(roleId) - .then(role => { - if (!role) { - throw new Error('Invalid Role'); - } - - return database.addAllowedRole({ - guild_id: msg.guild.id, - role_id: role.id - }); - }) - .then(() => msg.reply(`Role <@&${roleId}> can now configure me!`)) - .catch(err => { - if (err.message === 'Invalid Role') { - msg.reply(`I can't find a role with ID \`${roleId}\``); - } - else if (err.message.includes('UNIQUE constraint failed')) { - msg.reply(`Role <@&${roleId}> was already added.`); - } - else { - msg.reply(`I got an error I don't recognize:\n\`${err.message}\``); - logError(err, 'For message', msg.content); - } - }); -} - -/** - * Removes a role from being allowed to configure this bot's settings for a - * guild. A role can remove itself, which is dumb but whatever. - */ -function removePermissionRole(msg, parts) { - let maybeRole = parts.shift(); - let roleId = extractId(maybeRole); - - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - else if (!maybeRole) issue = 'Missing role!'; - else if (!roleId) issue = `Invalid role \`${maybeRole}\`!`; - - if (issue) { - msg.reply(issue + usage('perm-remove')); - return; - } - - msg.guild.roles.fetch(roleId) - .then(role => { - if (!role) { - throw new Error('Invalid Role'); - } - - return database.removeAllowedRole({ - guild_id: msg.guild.id, - role_id: role.id - }); - }) - .then(numRemoved => msg.reply( - `Role <@&${roleId}> ${ - numRemoved === 1 ? 'is no longer' : 'was already not' - } allowed to configure me.` - )) - .catch(err => { - if (err.message === 'Invalid Role') { - msg.reply(`I can't find a role with ID \`${roleId}\``); - } - else { - msg.reply(`I got an error I don't recognize:\n\`${err.message}\``); - logError(err, 'For message', msg.content); - } - }); -} - -/** - * Makes two roles mutually exclusive for a guild. - */ -function addMutexRoles(msg, parts) { - let maybeRole1 = parts.shift(); - let maybeRole2 = parts.shift(); - - let roleId1 = extractId(maybeRole1); - let roleId2 = extractId(maybeRole2); - - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - else if (!maybeRole1) issue = 'Missing role 1!'; - else if (!maybeRole2) issue = 'Missing role 2!'; - else if (!roleId1) issue = `Invalid role \`${maybeRole1}\`!`; - else if (!roleId2) issue = `Invalid role \`${maybeRole2}\`!`; - else if (roleId1 == roleId2) - issue = 'Cannot make a role mutually exclusive with itself!'; - - if (issue) { - msg.reply(issue + usage('mutex-add')); - return; - } - - Promise.all([ - msg.guild.roles.fetch(roleId1), - msg.guild.roles.fetch(roleId2) - ]) - .then(([role1, role2]) => { - if (!role1) throw new Error('Invalid Role 1'); - if (!role2) throw new Error('Invalid Role 2'); - - return database.addMutexRole({ - guild_id: msg.guild.id, - role_id_1: role1.id, - role_id_2: role2.id - }); - }) - .then(() => msg.reply( - `Roles <@&${roleId1}> and <@&${roleId2}> are now mutually exclusive` - )) - .catch(err => { - let match; - if (match = err.message.match(/Invalid Role (\d)/)) { - let cantFind = match[1] === '1' ? roleId1 : roleId2; - msg.reply(`I can't find a role with ID \`${cantFind}\``); - } - else if (err.message.includes('UNIQUE constraint failed')) { - msg.reply(unindent( - `Roles <@&${roleId1}> and <@&${roleId2}> are - already mutually exclusive` - )); - } - else { - msg.reply(`I got an error I don't recognize:\n\`${err.message}\``); - logError(err, 'For message', msg.content); - } - }); -} - -/** - * Removes the mutually exclusive restriction for two roles in a guild. - */ -function removeMutexRoles(msg, parts) { - let maybeRole1 = parts.shift(); - let maybeRole2 = parts.shift(); - - let roleId1 = extractId(maybeRole1); - let roleId2 = extractId(maybeRole2); - - let issue; - if (parts.length > 0) issue = 'Too many arguments!'; - else if (!maybeRole1) issue = 'Missing role 1!'; - else if (!maybeRole2) issue = 'Missing role 2!'; - else if (!roleId1) issue = `Invalid role \`${maybeRole1}\`!`; - else if (!roleId2) issue = `Invalid role \`${maybeRole2}\`!`; - - if (issue) { - msg.reply(issue + usage('mutex-remove')); - return; - } - - Promise.all([ - msg.guild.roles.fetch(roleId1), - msg.guild.roles.fetch(roleId2) - ]) - .then(([role1, role2]) => { - if (!role1) throw new Error('Invalid Role 1'); - if (!role2) throw new Error('Invalid Role 2'); - - return database.removeMutexRole({ - guild_id: msg.guild.id, - role_id_1: role1.id, - role_id_2: role2.id - }); - }) - .then(numRemoved => msg.reply( - `Roles <@&${roleId1}> and <@&${roleId2}> ${ - numRemoved === 1 ? 'are no longer' : 'were already not' - } mutually exclusive` - )) - .catch(err => { - let match; - if (match = err.message.match(/Invalid Role (\d)/)) { - let cantFind = match[1] === '1' ? roleId1 : roleId2; - msg.reply(`I can't find a role with ID \`${cantFind}\``); - } - else { - msg.reply(`I got an error I don't recognize:\n\`${err.message}\``); - logError(err, 'For message', msg.content); - } - }); -} - -/** - * Replies with info about this bot, including a link to the source code to be - * compliant with the AGPLv3 this bot is licensed under. - */ -function sayInfo(msg) { - database.getMetaStats().then(stats => msg.reply( - `${info.description}\n` + - `**Running version:** ${info.version}\n` + - `**Source code:** ${info.homepage}\n\n` + - '```Stats For Nerds\n' + - ` - Servers bot is active in: ${stats.guilds}\n` + - ` - Reaction role mappings: ${stats.roles}\n` + - ` - Total role assignments: ${stats.assignments}\n` + - '```' - )); -} - -/** - * Replies with a list of commands and their usage information. - */ -function sayHelp(msg) { - let embed = new Discord.MessageEmbed() - .setTitle('Commands Help'); - - COMMANDS.forEach(def => embed.addField( - def.get('usage'), def.get('description') - )); - - msg.reply(embed).catch(logError); -} - -/** - * Event handler for when a reaction is added to a message. - * Checks if the message has any reaction roles configured, assigning a role to - * the user who added the reaction, if applicable. Ignores reacts added by this - * bot, of course. If a user attempts to assign a role that is mutually - * exclusive with a role they already have, they will lose that first role, and - * their reaction to the message for that role will be removed. - */ -function onReactionAdd(reaction, user) { - if (user === client.user) { - return; - } - - let emoji = emojiIdFromEmoji(reaction.emoji); - - cache.getReactRole(reaction.message.id, emoji) - .then(roleId => { - if (!roleId) { - return; - } - - // TODO if ever there was a time for async-await, this is it. - // TODO Also this seems to hit Discord's rate limit almost - // immediately because of each mutex react removal being its own - // request. Might need to look into .set - return Promise.all([ - reaction.message.guild.members.fetch(user.id), - database.getMutexRoles({ - guild_id: reaction.message.guild.id, - role_id: roleId - }) - ]) - .then(([member, mutexRoles]) => - member.roles.remove(mutexRoles, 'Role bot removal (mutex)') - .then(() => member.roles.add(roleId, 'Role bot assignment')) - .then(() => database.getMutexEmojis(mutexRoles)) - .then(mutexEmojis => - asyncForEach(mutexEmojis, function(emoji) { - let mesRec = reaction.message.reactions.resolve(emoji); - if (mesRec) return mesRec.users.remove(user); - }) - ) - ) - .then(() => database.incrementAssignCounter()) - .then(() => console.log(`added role ${roleId} to ${user}`)); - }) - .catch(logError); -} - -/** - * Event handler for when a reaction is removed from a message. - * Checks if the message has any reaction roles configured, removing a role from - * the user who removed their reaction, if applicable. Ignored reacts removed by - * this bot, of course. - */ -function onReactionRemove(reaction, user) { - // TODO How do we handle two emojis mapped to the same role? - // Do we only remove the role if the user doesn't have any of the mapped - // reactions? Or do we remove when any of the emojis are un-reacted? - - if (user === client.user) { - return; - } - - let emoji = emojiIdFromEmoji(reaction.emoji); - - cache.getReactRole(reaction.message.id, emoji) - .then(roleId => { - if (!roleId) { - return; - } - - return reaction.message.guild.members.fetch(user.id) - .then(member => member.roles.remove(roleId, 'Role bot removal')) - .then(() => console.log(`removed role ${roleId} from ${user}`)) - }) - .catch(logError); -} - -/** - * Given a message, determines if the sender has permission to use the bot. - * This may involve a database check, so it returns a Promise. - */ -function userHasPermission(msg, command_name) { - const everyoneCommands = ['info']; - if ( - msg.member.hasPermission(Discord.Permissions.FLAGS.ADMINISTRATOR) || - everyoneCommands.includes(command_name) - ) { - return Promise.resolve(true); - } - - return database.getAllowedRoles(msg.guild.id) - .then(roles => roles.some(role => msg.member.roles.cache.has(role))); -} - -/** - * Takes an array of items and a function that may return a promise, then runs - * the promise function for each item in the array, in sequence. Returns a - * promise that resolves once every element has been processed. - * Does not return the results because we don't need them. - */ -async function asyncForEach(arr, promiseFunc) { - for await (let elem of arr) { - promiseFunc(elem); - } -} - -// I'm aware Discord.MessageMentions.*_PATTERN constants exist, but they all -// have the global flag set, which screws up matching groups. For this reason we -// need to construct our own. -// -// Also, for flexibility's sake we just don't care about what type of ID this -// is. This could have collisions but it's unlikely. -function extractId(str) { - if (!str) { - return null; - } - - let match = str.match(/(\d{17,19})/); - return match ? match[1] : null; -} - -/** - * Allows us to handle custom server emojis. They are encoded in messages like - * this: <:flagtg:681985787864416286>. Discord.js can add emojis using a - * unicode string for built-in emojis, or the ID portion of the name - * (e.g. 681985787864416286) for custom server emojis. - */ -function extractEmoji(emoji) { - if (!emoji) { - return null; - } - - let match = emoji.match(//); - return match ? match[1] : emoji; -} - -/** - * Built-in emojis are identified by name. Custom emojis are identified by ID. - * This function handles that nuance for us. - */ -function emojiIdFromEmoji(emoji) { - return emoji.id || emoji.name; -} - -/** - * Helper for creating command definitions. We could nest these in raw objects, - * but Maps are nicer. - */ -function cmdDef(handler, name, usage, description) { - let map = new Map(); - map.set('handler', handler); - map.set('usage', `\`${name} ${usage}\``); - map.set('description', unindent(description)); - COMMANDS.set(name, map); -} - -/** - * Dress up usage string for a command. - */ -function usage(name) { - return `\nUsage: ${COMMANDS.get(name).get('usage')}`; -} - -/** - * Allows us to treat multi-line template strings as a single continuous line. - */ -function unindent(str) { - return str - .replace(/^\s*/, '') - .replace(/\n\t*/g, ' '); -} - -/** - * Single function to make error redirection easier in the future. - * Maybe some day we'll do something more intelligent with errors. - */ -function logError(err) { - console.error(err); -} - diff --git a/migrations/20200225015300_db.js b/migrations/20200225015300_db.js deleted file mode 100644 index 92f9254..0000000 --- a/migrations/20200225015300_db.js +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -const db = require('../database'); - -exports.up = function(knex) { - return knex.schema.createTable(db.REACTS, table => { - table.string('guild_id', db.DISCORD_ID_LENGTH.MAX); - table.string('message_id', db.DISCORD_ID_LENGTH.MAX); - table.string('emoji_id', db.DISCORD_ID_LENGTH.MAX); - table.string('role_id', db.DISCORD_ID_LENGTH.MAX); - - table.primary(['message_id', 'emoji_id']); - }); -}; - -exports.down = function(knex) { - return knex.schema.dropTable(db.REACTS); -}; - diff --git a/migrations/20200814190000_meta.js b/migrations/20200814190000_meta.js deleted file mode 100644 index 90957cb..0000000 --- a/migrations/20200814190000_meta.js +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -const db = require('../database'); - -exports.up = function(knex) { - return knex.schema.createTable(db.META, table => { - table.integer('assignments'); - }).then(() => { - // There will only ever be one row in this table so we make it here. - return knex(db.META).insert({ assignments: 0 }); - }); -}; - -exports.down = function(knex) { - return knex.schema.dropTable(db.META); -}; - diff --git a/migrations/20200815043000_perms.js b/migrations/20200815043000_perms.js deleted file mode 100644 index 381680e..0000000 --- a/migrations/20200815043000_perms.js +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -const db = require('../database'); - -exports.up = function(knex) { - return knex.schema.createTable(db.PERMS, table => { - table.string('guild_id', db.DISCORD_ID_LENGTH.MAX); - table.string('role_id', db.DISCORD_ID_LENGTH.MAX); - - table.primary(['guild_id', 'role_id']); - }); -}; - -exports.down = function(knex) { - return knex.schema.dropTable(db.PERMS); -}; - diff --git a/migrations/20200818203000_mutex.js b/migrations/20200818203000_mutex.js deleted file mode 100644 index c869b34..0000000 --- a/migrations/20200818203000_mutex.js +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. - * Copyright (C) 2020 Mimickal (Mia Moretti). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - ******************************************************************************/ -const db = require('../database'); - -exports.up = function(knex) { - return knex.schema.createTable(db.MUTEX, table => { - table.string('guild_id', db.DISCORD_ID_LENGTH.MAX); - table.string('role_id_1', db.DISCORD_ID_LENGTH.MAX); - table.string('role_id_2', db.DISCORD_ID_LENGTH.MAX); - - table.primary(['guild_id', 'role_id_1', 'role_id_2']); - }); -}; - -exports.down = function(knex) { - return knex.schema.dropTable(db.MUTEX); -}; - diff --git a/package-lock.json b/package-lock.json index a1ade16..8046a39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,30 +1,147 @@ { "name": "reactionrolebot", - "version": "1.1.4", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "reactionrolebot", - "version": "1.1.2", + "version": "2.0.0", "license": "AGPL-3.0", "dependencies": { - "discord.js": "^12.5.1", + "discord-command-registry": "^1.2.0", + "discord.js": "^13.3.1", "knex": "^0.20.13", "lodash": "^4.17.20", - "sqlite3": "^4.1.1" + "multimap": "^1.1.0", + "node-cache": "^5.1.2", + "sqlite3": "^4.2.0", + "winston": "^3.3.3" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@discordjs/builders": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.9.0.tgz", + "integrity": "sha512-XM/5yrTxMF0SDKza32YzGDQO1t+qEJTaF8Zvxu/UOjzoqzMPPGQBjC1VgZxz8/CBLygW5qI+UVygMa88z13G3g==", + "dependencies": { + "@sindresorhus/is": "^4.2.0", + "discord-api-types": "^0.24.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.1", + "zod": "^3.11.6" }, - "devDependencies": {} + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@discordjs/builders/node_modules/discord-api-types": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz", + "integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==", + "deprecated": "No longer supported. Install the latest release!", + "engines": { + "node": ">=12" + } }, "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": { + "node_modules/@discordjs/rest": { + "version": "0.1.0-canary.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.1.0-canary.0.tgz", + "integrity": "sha512-d+s//ISYVV+e0w/926wMEeO7vju+Pn11x1JM4tcmVMCHSDgpi6pnFCNAXF1TEdnDcy7xf9tq5cf2pQkb/7ySTQ==", + "dependencies": { + "@discordjs/collection": "^0.1.6", + "@sapphire/async-queue": "^1.1.4", + "@sapphire/snowflake": "^1.3.5", + "abort-controller": "^3.0.0", + "discord-api-types": "^0.18.1", + "form-data": "^4.0.0", + "node-fetch": "^2.6.1", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@discordjs/rest/node_modules/discord-api-types": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.18.1.tgz", + "integrity": "sha512-hNC38R9ZF4uaujaZQtQfm5CdQO58uhdkoHQAVvMfIL0LgOSZeW575W8H6upngQOuoxWd8tiRII3LLJm9zuQKYg==", + "deprecated": "No longer supported. Install the latest release (0.20.2)", + "engines": { + "node": ">=12" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.1.tgz", + "integrity": "sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-1.3.6.tgz", + "integrity": "sha512-QnzuLp+p9D7agynVub/zqlDVriDza9y3STArBhNiNBUgIX8+GL5FpQxstRfw1jDr5jkZUjcuKYAHxjIuXKdJAg==", + "deprecated": "This version has been automatically deprecated by @favware/npm-deprecate. Please use a newer version.", + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@types/node": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", + "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==" + }, + "node_modules/@types/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/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==", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -34,6 +151,14 @@ "node": ">= 6" } }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -50,17 +175,6 @@ "node": ">=6.5" } }, - "node_modules/ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "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-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -75,9 +189,9 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "node_modules/are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -131,22 +245,6 @@ "node": ">=0.10.0" } }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "engines": { - "node": ">=0.8" - } - }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -155,6 +253,11 @@ "node": ">=0.10.0" } }, + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -171,23 +274,10 @@ "node": ">= 4.5.0" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" - }, "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base": { "version": "0.11.2", @@ -217,54 +307,6 @@ "node": ">=0.10.0" } }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -305,6 +347,14 @@ "node": ">=0.10.0" } }, + "node_modules/braces/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -324,11 +374,6 @@ "node": ">=0.10.0" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -359,6 +404,79 @@ "node": ">=0.10.0" } }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "engines": { + "node": ">=0.8" + } + }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -379,11 +497,51 @@ "node": ">=0.10.0" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "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==", + "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=" + }, + "node_modules/color-string": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", + "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colorette": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==" }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -427,20 +585,9 @@ } }, "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/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/debug": { "version": "4.1.1", @@ -479,41 +626,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -546,34 +658,86 @@ "node": ">=0.10" } }, - "node_modules/discord.js": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", - "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", - "deprecated": "no longer supported", + "node_modules/discord-api-types": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.25.2.tgz", + "integrity": "sha512-O243LXxb5gLLxubu5zgoppYQuolapGVWPw3ll0acN0+O8TnPUE2kFp9Bt3sTRYodw8xFIknOVxjSeyWYBpVcEQ==", + "deprecated": "No longer supported. Install the latest release!", + "engines": { + "node": ">=12" + } + }, + "node_modules/discord-command-registry": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/discord-command-registry/-/discord-command-registry-1.2.0.tgz", + "integrity": "sha512-HqZd4owQ/prvhEDI60TbD2YqOhJ4HLFjBGUr9zPHcO0Aihvphn6RfAAc1Xqzad9rbsxGOcaTYBRFOpMtklVwgw==", "dependencies": { - "@discordjs/collection": "^0.1.6", - "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", + "@discordjs/builders": "^0.9.0", + "@discordjs/rest": "^0.1.0-canary.0", + "discord-api-types": "^0.25.2" + }, + "peerDependencies": { + "discord.js": "^13.1.0" + } + }, + "node_modules/discord.js": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.6.0.tgz", + "integrity": "sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==", + "dependencies": { + "@discordjs/builders": "^0.11.0", + "@discordjs/collection": "^0.4.0", + "@sapphire/async-queue": "^1.1.9", + "@types/node-fetch": "^2.5.12", + "@types/ws": "^8.2.2", + "discord-api-types": "^0.26.0", + "form-data": "^4.0.0", "node-fetch": "^2.6.1", - "prism-media": "^1.2.2", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.3.1" + "ws": "^8.4.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=16.6.0", + "npm": ">=7.0.0" } }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "node_modules/discord.js/node_modules/@discordjs/builders": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.11.0.tgz", + "integrity": "sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==", "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "@sindresorhus/is": "^4.2.0", + "discord-api-types": "^0.26.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.1", + "zod": "^3.11.6" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/discord.js/node_modules/@discordjs/collection": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.4.0.tgz", + "integrity": "sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, + "node_modules/discord.js/node_modules/discord-api-types": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.26.1.tgz", + "integrity": "sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -637,6 +801,79 @@ "node": ">=0.10.0" } }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expand-brackets/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -670,17 +907,6 @@ "node": ">=0.10.0" } }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -721,58 +947,18 @@ "node": ">=0.10.0" } }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "engines": { "node": ">=0.10.0" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/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==" - }, - "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==" + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, "node_modules/fill-range": { "version": "4.0.0", @@ -799,6 +985,14 @@ "node": ">=0.10.0" } }, + "node_modules/fill-range/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -836,6 +1030,11 @@ "node": ">= 0.10" } }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -855,12 +1054,17 @@ "node": ">=0.10.0" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, "engines": { - "node": "*" + "node": ">= 6" } }, "node_modules/fragment-cache": { @@ -887,6 +1091,11 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -915,18 +1124,10 @@ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -970,25 +1171,15 @@ "node": ">=0.10.0" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "deprecated": "this library is no longer supported", + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dependencies": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "function-bind": "^1.1.1" }, "engines": { - "node": ">=6" + "node": ">= 0.4.0" } }, "node_modules/has-unicode": { @@ -1043,20 +1234,6 @@ "node": ">=0.10.0" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1069,9 +1246,9 @@ } }, "node_modules/ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", "dependencies": { "minimatch": "^3.0.4" } @@ -1096,9 +1273,9 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/interpret": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.0.0.tgz", - "integrity": "sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "engines": { "node": ">= 0.10" } @@ -1115,80 +1292,69 @@ "node": ">=0.10.0" } }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dependencies": { - "is-buffer": "^1.1.5" + "kind-of": "^6.0.0" }, "engines": { "node": ">=0.10.0" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dependencies": { - "kind-of": "^3.0.2" + "has": "^1.0.3" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dependencies": { - "is-buffer": "^1.1.5" + "kind-of": "^6.0.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, "engines": { "node": ">=0.10.0" } @@ -1213,9 +1379,9 @@ } }, "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==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dependencies": { "is-extglob": "^2.1.1" }, @@ -1267,10 +1433,16 @@ "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/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/is-unc-path": { "version": "1.0.0", @@ -1309,45 +1481,6 @@ "node": ">=0.10.0" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "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==" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -1357,9 +1490,9 @@ } }, "node_modules/knex": { - "version": "0.20.13", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.13.tgz", - "integrity": "sha512-YVl//Te0G5suc+d9KyeI6WuhtgVlxu6HXYQB+WqrccFkSZAbHqlqZlUMogYG3UoVq69c3kiFbbxgUNkrO0PVfg==", + "version": "0.20.15", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.15.tgz", + "integrity": "sha512-WHmvgfQfxA5v8pyb9zbskxCS1L1WmYgUbwBhHojlkmdouUOazvroUWlCr6KIKMQ8anXZh1NXOOtIUMnxENZG5Q==", "dependencies": { "colorette": "1.1.0", "commander": "^4.1.1", @@ -1408,13 +1541,10 @@ } } }, - "node_modules/knex/node_modules/uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==", - "bin": { - "uuid": "dist/bin/uuid" - } + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, "node_modules/liftoff": { "version": "3.1.0", @@ -1439,6 +1569,18 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/logform": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz", + "integrity": "sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==", + "dependencies": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, "node_modules/make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -1493,28 +1635,28 @@ } }, "node_modules/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.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/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.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.43.0" + "mime-db": "1.52.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==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1523,9 +1665,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minipass": { "version": "2.9.0", @@ -1556,37 +1698,31 @@ "node": ">=0.10.0" } }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "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==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/multimap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz", + "integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==" }, "node_modules/nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" }, "node_modules/nanomatch": { "version": "1.2.13", @@ -1610,9 +1746,9 @@ } }, "node_modules/needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", "dependencies": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -1626,20 +1762,41 @@ } }, "node_modules/needle/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "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/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "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-pre-gyp": { @@ -1676,9 +1833,9 @@ } }, "node_modules/npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", "dependencies": { "npm-normalize-package-bin": "^1.0.1" } @@ -1717,14 +1874,6 @@ "node": ">=0.10.0" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1757,6 +1906,49 @@ "node": ">=0.10.0" } }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-copy/node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -1824,6 +2016,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -1910,11 +2110,6 @@ "node": ">=0.10.0" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "node_modules/pg-connection-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.1.0.tgz", @@ -1928,57 +2123,11 @@ "node": ">=0.10.0" } }, - "node_modules/prism-media": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", - "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==", - "peerDependencies": { - "@discordjs/opus": "^0.3.3", - "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", - "node-opus": "^0.3.3", - "opusscript": "^0.0.7" - }, - "peerDependenciesMeta": { - "@discordjs/opus": { - "optional": true - }, - "ffmpeg-static": { - "optional": true - }, - "node-opus": { - "optional": true - }, - "opusscript": { - "optional": true - } - } - }, "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/psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" - }, - "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==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1993,11 +2142,6 @@ "rc": "cli.js" } }, - "node_modules/rc/node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -2036,9 +2180,9 @@ } }, "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", "engines": { "node": ">=0.10.0" } @@ -2051,56 +2195,17 @@ "node": ">=0.10" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dependencies": { - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2156,6 +2261,14 @@ "ret": "~0.1.10" } }, + "node_modules/safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2204,15 +2317,26 @@ "node": ">=0.10.0" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dependencies": { + "is-arrayish": "^0.3.1" + } }, "node_modules/snapdragon": { "version": "0.8.2", @@ -2243,49 +2367,14 @@ }, "engines": { "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-descriptor": "^1.0.0" }, "engines": { "node": ">=0.10.0" @@ -2343,6 +2432,79 @@ "node": ">=0.10.0" } }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/snapdragon/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2360,6 +2522,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", "dependencies": { "atob": "^2.1.2", "decode-uri-component": "^0.2.0", @@ -2369,9 +2532,10 @@ } }, "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated" }, "node_modules/split-string": { "version": "3.1.0", @@ -2385,45 +2549,23 @@ } }, "node_modules/sqlite3": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz", - "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", + "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", "hasInstallScript": true, "dependencies": { "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0", - "request": "^2.87.0" + "node-pre-gyp": "^0.11.0" } }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/sshpk/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -2447,6 +2589,71 @@ "node": ">=0.10.0" } }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2487,6 +2694,17 @@ "node": ">=0.10.0" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tar": { "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", @@ -2531,6 +2749,11 @@ "node": ">=8.0.0" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/tildify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", @@ -2587,33 +2810,25 @@ "node": ">=0.10.0" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "node_modules/ts-mixer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz", + "integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==" + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/unc-path-regex": { "version": "0.1.2", @@ -2637,6 +2852,14 @@ "node": ">=0.10.0" } }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -2681,14 +2904,6 @@ "node": ">=0.10.0" } }, - "node_modules/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==", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -2709,18 +2924,17 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", "dependencies": { "homedir-polyfill": "^1.0.1" }, @@ -2728,17 +2942,18 @@ "node": ">= 0.10" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "engines": [ - "node >=0.6.0" - ], + "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/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "node_modules/which": { @@ -2753,11 +2968,70 @@ } }, "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/winston": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.7.2.tgz", + "integrity": "sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng==", + "dependencies": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport/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/winston/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": { - "string-width": "^1.0.2 || 2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/wrappy": { @@ -2766,45 +3040,148 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/zod": { + "version": "3.14.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.14.4.tgz", + "integrity": "sha512-U9BFLb2GO34Sfo9IUYp0w3wJLlmcyGoMd75qU9yf+DrdGA4kEx6e+l9KOkAlyUO0PSQzZCa3TR4qVlcmwqSDuw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + }, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@discordjs/builders": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.9.0.tgz", + "integrity": "sha512-XM/5yrTxMF0SDKza32YzGDQO1t+qEJTaF8Zvxu/UOjzoqzMPPGQBjC1VgZxz8/CBLygW5qI+UVygMa88z13G3g==", + "requires": { + "@sindresorhus/is": "^4.2.0", + "discord-api-types": "^0.24.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.1", + "zod": "^3.11.6" + }, + "dependencies": { + "discord-api-types": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz", + "integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==" + } + } + }, + "@discordjs/collection": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", + "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" + }, + "@discordjs/rest": { + "version": "0.1.0-canary.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.1.0-canary.0.tgz", + "integrity": "sha512-d+s//ISYVV+e0w/926wMEeO7vju+Pn11x1JM4tcmVMCHSDgpi6pnFCNAXF1TEdnDcy7xf9tq5cf2pQkb/7ySTQ==", + "requires": { + "@discordjs/collection": "^0.1.6", + "@sapphire/async-queue": "^1.1.4", + "@sapphire/snowflake": "^1.3.5", + "abort-controller": "^3.0.0", + "discord-api-types": "^0.18.1", + "form-data": "^4.0.0", + "node-fetch": "^2.6.1", + "tslib": "^2.3.0" + }, + "dependencies": { + "discord-api-types": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.18.1.tgz", + "integrity": "sha512-hNC38R9ZF4uaujaZQtQfm5CdQO58uhdkoHQAVvMfIL0LgOSZeW575W8H6upngQOuoxWd8tiRII3LLJm9zuQKYg==" + } + } + }, + "@sapphire/async-queue": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.1.tgz", + "integrity": "sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g==" + }, + "@sapphire/snowflake": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-1.3.6.tgz", + "integrity": "sha512-QnzuLp+p9D7agynVub/zqlDVriDza9y3STArBhNiNBUgIX8+GL5FpQxstRfw1jDr5jkZUjcuKYAHxjIuXKdJAg==" + }, + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" + }, + "@types/node": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", + "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==" + }, + "@types/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } } } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - }, - "dependencies": { - "@discordjs/collection": { - "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", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "@types/node": "*" } }, "abbrev": { @@ -2820,17 +3197,6 @@ "event-target-shim": "^5.0.0" } }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -2842,9 +3208,9 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -2880,24 +3246,16 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2908,20 +3266,10 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" - }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", @@ -2944,47 +3292,6 @@ "requires": { "is-descriptor": "^1.0.0" } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" } } }, @@ -3021,6 +3328,11 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" } } }, @@ -3040,11 +3352,6 @@ "unset-value": "^1.0.0" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -3068,9 +3375,65 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -3085,11 +3448,51 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "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==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", + "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "colorette": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==" }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3124,17 +3527,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "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=" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "debug": { "version": "4.1.1", @@ -3161,34 +3556,6 @@ "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } } }, "delayed-stream": { @@ -3211,30 +3578,66 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, - "discord.js": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", - "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "discord-api-types": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.25.2.tgz", + "integrity": "sha512-O243LXxb5gLLxubu5zgoppYQuolapGVWPw3ll0acN0+O8TnPUE2kFp9Bt3sTRYodw8xFIknOVxjSeyWYBpVcEQ==" + }, + "discord-command-registry": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/discord-command-registry/-/discord-command-registry-1.2.0.tgz", + "integrity": "sha512-HqZd4owQ/prvhEDI60TbD2YqOhJ4HLFjBGUr9zPHcO0Aihvphn6RfAAc1Xqzad9rbsxGOcaTYBRFOpMtklVwgw==", "requires": { - "@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.2", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.3.1" + "@discordjs/builders": "^0.9.0", + "@discordjs/rest": "^0.1.0-canary.0", + "discord-api-types": "^0.25.2" } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "discord.js": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.6.0.tgz", + "integrity": "sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==", "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "@discordjs/builders": "^0.11.0", + "@discordjs/collection": "^0.4.0", + "@sapphire/async-queue": "^1.1.9", + "@types/node-fetch": "^2.5.12", + "@types/ws": "^8.2.2", + "discord-api-types": "^0.26.0", + "form-data": "^4.0.0", + "node-fetch": "^2.6.1", + "ws": "^8.4.0" + }, + "dependencies": { + "@discordjs/builders": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.11.0.tgz", + "integrity": "sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==", + "requires": { + "@sindresorhus/is": "^4.2.0", + "discord-api-types": "^0.26.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.1", + "zod": "^3.11.6" + } + }, + "@discordjs/collection": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.4.0.tgz", + "integrity": "sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==" + }, + "discord-api-types": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.26.1.tgz", + "integrity": "sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==" + } } }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -3283,6 +3686,62 @@ "is-extendable": "^0.1.0" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3310,16 +3769,6 @@ "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "extglob": { @@ -3353,48 +3802,17 @@ "is-extendable": "^0.1.0" } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "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==" - }, - "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==" + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, "fill-range": { "version": "4.0.0", @@ -3414,6 +3832,11 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" } } }, @@ -3445,6 +3868,11 @@ "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -3458,10 +3886,15 @@ "for-in": "^1.0.1" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } }, "fragment-cache": { "version": "0.2.1", @@ -3484,6 +3917,11 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -3509,18 +3947,10 @@ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3552,18 +3982,12 @@ "which": "^1.2.14" } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "function-bind": "^1.1.1" } }, "has-unicode": { @@ -3608,16 +4032,6 @@ "parse-passwd": "^1.0.0" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3627,9 +4041,9 @@ } }, "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", "requires": { "minimatch": "^3.0.4" } @@ -3654,9 +4068,9 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "interpret": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.0.0.tgz", - "integrity": "sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==" }, "is-absolute": { "version": "1.0.0", @@ -3668,67 +4082,56 @@ } }, "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "kind-of": "^6.0.0" } }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "kind-of": "^6.0.0" } }, "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } }, "is-extglob": { "version": "2.1.1", @@ -3744,9 +4147,9 @@ } }, "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==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { "is-extglob": "^2.1.1" } @@ -3785,10 +4188,10 @@ "is-unc-path": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "is-unc-path": { "version": "1.0.0", @@ -3818,51 +4221,15 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "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==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "knex": { - "version": "0.20.13", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.13.tgz", - "integrity": "sha512-YVl//Te0G5suc+d9KyeI6WuhtgVlxu6HXYQB+WqrccFkSZAbHqlqZlUMogYG3UoVq69c3kiFbbxgUNkrO0PVfg==", + "version": "0.20.15", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.15.tgz", + "integrity": "sha512-WHmvgfQfxA5v8pyb9zbskxCS1L1WmYgUbwBhHojlkmdouUOazvroUWlCr6KIKMQ8anXZh1NXOOtIUMnxENZG5Q==", "requires": { "colorette": "1.1.0", "commander": "^4.1.1", @@ -3879,15 +4246,13 @@ "tildify": "2.0.0", "uuid": "^7.0.1", "v8flags": "^3.1.3" - }, - "dependencies": { - "uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==" - } } }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -3908,6 +4273,18 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "logform": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz", + "integrity": "sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==", + "requires": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -3950,30 +4327,30 @@ } }, "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.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "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.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.43.0" + "mime-db": "1.52.0" } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "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==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minipass": { "version": "2.9.0", @@ -3999,35 +4376,30 @@ "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "multimap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz", + "integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==" }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" }, "nanomatch": { "version": "1.2.13", @@ -4048,9 +4420,9 @@ } }, "needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -4058,19 +4430,30 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" } } } }, + "node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "requires": { + "clone": "2.x" + } + }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } }, "node-pre-gyp": { "version": "0.11.0", @@ -4099,9 +4482,9 @@ } }, "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", "requires": { "npm-normalize-package-bin": "^1.0.1" } @@ -4137,11 +4520,6 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4165,6 +4543,39 @@ "is-descriptor": "^0.1.0" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -4219,6 +4630,14 @@ "wrappy": "1" } }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -4281,11 +4700,6 @@ "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=" }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "pg-connection-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.1.0.tgz", @@ -4296,32 +4710,11 @@ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, - "prism-media": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", - "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==", - "requires": {} - }, "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==" }, - "psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -4331,13 +4724,6 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - } } }, "readable-stream": { @@ -4372,60 +4758,23 @@ } }, "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } - } - }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "requires": { - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-dir": { @@ -4468,6 +4817,11 @@ "ret": "~0.1.10" } }, + "safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4506,18 +4860,26 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" } } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + } }, "snapdragon": { "version": "0.8.2", @@ -4558,6 +4920,62 @@ "is-extendable": "^0.1.0" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4582,32 +5000,6 @@ "requires": { "is-descriptor": "^1.0.0" } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } } } }, @@ -4647,9 +5039,9 @@ } }, "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, "split-string": { "version": "3.1.0", @@ -4660,37 +5052,18 @@ } }, "sqlite3": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz", - "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", + "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", "requires": { "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0", - "request": "^2.87.0" + "node-pre-gyp": "^0.11.0" } }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - } - } + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "static-extend": { "version": "0.1.2", @@ -4708,6 +5081,57 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, @@ -4742,6 +5166,11 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, "tar": { "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", @@ -4768,6 +5197,11 @@ "resolved": "https://registry.npmjs.org/tarn/-/tarn-2.0.0.tgz", "integrity": "sha512-7rNMCZd3s9bhQh47ksAQd92ADFcJUjjbyOvyFjNLwTPpGieFHMC84S+LOzw0fx1uh6hnDz/19r8CPMnIjJlMMA==" }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "tildify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", @@ -4811,27 +5245,25 @@ "repeat-string": "^1.6.1" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "ts-mixer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz", + "integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==" + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "unc-path-regex": { "version": "0.1.2", @@ -4847,6 +5279,13 @@ "get-value": "^2.0.6", "is-extendable": "^0.1.1", "set-value": "^2.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + } } }, "unset-value": { @@ -4885,14 +5324,6 @@ } } }, - "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==", - "requires": { - "punycode": "^2.1.0" - } - }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -4909,26 +5340,30 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" }, "v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", "requires": { "homedir-polyfill": "^1.0.1" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "which": { @@ -4940,11 +5375,62 @@ } }, "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "winston": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.7.2.tgz", + "integrity": "sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng==", + "requires": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + }, + "dependencies": { + "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" + } + } + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", "requires": { - "string-width": "^1.0.2 || 2" + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "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" + } + } } }, "wrappy": { @@ -4953,15 +5439,20 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", "requires": {} }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "zod": { + "version": "3.14.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.14.4.tgz", + "integrity": "sha512-U9BFLb2GO34Sfo9IUYp0w3wJLlmcyGoMd75qU9yf+DrdGA4kEx6e+l9KOkAlyUO0PSQzZCa3TR4qVlcmwqSDuw==" } } } diff --git a/package.json b/package.json index 8e0ffc1..d028b90 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,23 @@ { "name": "reactionrolebot", - "version": "1.1.4", + "version": "2.0.0", "description": "I'm a basic, no BS Discord bot that can assign and unassign roles using message reactions. I am completely free and open source, and always will be.", - "main": "main.js", + "main": "src/main.js", "dependencies": { - "discord.js": "^12.5.1", + "discord-command-registry": "^1.2.0", + "discord.js": "^13.3.1", "knex": "^0.20.13", "lodash": "^4.17.20", - "sqlite3": "^4.1.1" + "multimap": "^1.1.0", + "node-cache": "^5.1.2", + "sqlite3": "^4.2.0", + "winston": "^3.3.3" }, - "devDependencies": {}, "scripts": { - "knex": "node_modules/knex/bin/cli.js", + "start": "node .", + "dev": "node . dev-config.json", + "knex": "npx knex --knexfile src/knexfile.js", + "register": "node src/register.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { diff --git a/resources/post-receive b/resources/post-receive index 29a18a1..d046252 100755 --- a/resources/post-receive +++ b/resources/post-receive @@ -11,7 +11,7 @@ NODE_ENV="prod" mkdir -p $TEMP /usr/bin/env git --work-tree=$TEMP --git-dir=$REPO checkout -f cd $TEMP -/usr/bin/env npm install +/usr/bin/env npm ci NODE_ENV=$NODE_ENV /usr/bin/env npm run knex migrate:latest # Now install the code diff --git a/resources/reactionrolebot.service b/resources/reactionrolebot.service index 0fae6d1..4e5968c 100644 --- a/resources/reactionrolebot.service +++ b/resources/reactionrolebot.service @@ -11,7 +11,10 @@ Restart=on-failure RestartSec=10 User= Environment="NODE_ENV=prod" -ExecStart=/usr/bin/env nodejs /srv/discord/ReactionRoleBot/main.js +ExecStart=/usr/bin/env npm start --prefix /srv/discord/ReactionRoleBot/ +# If using nvm, you may need to do something like this instead: +#Environment="NODE_ENV=prod" "NODE_VERSION=" +#ExecStart= npm start --prefix /srv/discord/ReactionRoleBot [Install] WantedBy=multi-user.target diff --git a/src/commands.js b/src/commands.js new file mode 100644 index 0000000..2cee92c --- /dev/null +++ b/src/commands.js @@ -0,0 +1,781 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ + +// Some notes for this file: +// +// - Always call message.fetch(). +// This ensures Discord.js' caches (e.g. for reactions) are populated and up +// to date before doing anything. +// +// - Always fail safe. +// If any part of an operation fails, every action taken during that +// operation should be rolled back. +// +// - UI updates should come last. +// Updating what the user sees (e.g. sending message, adding a reaction) +// should always be done after other actions. Discord's client already shows +// a spinner while the bot is active, so we only need to confirm success or +// failure. +// The only exception to this is committing an active database transaction. + +const { + ApplicationCommandType, + Options, + SlashCommandRegistry, + bold, + codeBlock, + roleMention, +} = require('discord-command-registry'); +const Discord = require('discord.js'); +const NodeCache = require('node-cache'); + +const database = require('./database'); +const { rethrowHandled } = database; +const info = require('../package.json'); +const logger = require('./logger'); +const { + asLines, + emojiToKey, + entries, + ephemEdit, + ephemReply, + stringify, + unindent, +} = require('./util'); + + +const ONE_HOUR_IN_SECONDS = 60*60; +const CACHE_SETTINGS = { + stdTTL: ONE_HOUR_IN_SECONDS, + checkperiod: ONE_HOUR_IN_SECONDS, + useClones: false, +} +const SELECTED_MESSAGE_CACHE = new NodeCache(CACHE_SETTINGS); +const CLONE_MESSAGE_CACHE = new NodeCache(CACHE_SETTINGS); + +const REGISTRY = new SlashCommandRegistry() + .addCommand(command => command + .setName('info') + .setDescription( + 'Prints description, version, and link to source code for the bot' + ) + .setHandler(cmdInfo) + ) + .addContextMenuCommand(command => command + .setName('select-message') + .setType(ApplicationCommandType.Message) + .setHandler(requireAuth(cmdSelect)) + ) + .addContextMenuCommand(command => command + .setName('select-copy-target') + .setType(ApplicationCommandType.Message) + .setHandler(requireAuth(cmdSelectCopy)) + ) + .addCommand(command => command + .setName('select-message-mobile') + .setDescription('Workaround for selecting messages on mobile') + .setHandler(requireAuth(cmdSelectMobile)) + .addStringOption(option => option + .setName('message-url') + .setDescription('The URL for the message to select') + .setRequired(true) + ) + ) + .addCommand(command => command + .setName('selected') + .setDescription('Shows currently selected message') + .setHandler(requireAuth(cmdSelected)) + ) + .addCommand(command => command + .setName('copy') + .setDescription('Copy react-role mappings to another message') + .addSubcommand(subcommand => subcommand + .setName('select-target-mobile') + .setDescription('Workaround for selecting copy target message on mobile') + .setHandler(requireAuth(cmdSelectCopyMobile)) + .addStringOption(option => option + .setName('message-url') + .setDescription('The URL for the clone target message to select') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => subcommand + .setName('selected-target') + .setDescription('Shows currently selected copy target message') + .setHandler(requireAuth(cmdSelectedCopy)) + ) + .addSubcommand(subcommand => subcommand + .setName('execute') + .setDescription( + 'Copy role-react mappings from selected message to target message' + ) + .setHandler(requireAuth(cmdCopyMappings)) + ) + ) + .addCommand(command => command + .setName('role') + .setDescription('Manage react roles') + .addSubcommand(subcommand => subcommand + .setName('add') + .setDescription('Add a new react-role to the selected message') + .setHandler(requireAuth(cmdRoleAdd)) + .addStringOption(option => option + .setName('emoji') + .setDescription('The emoji to map the role to') + .setRequired(true) + ) + .addRoleOption(option => option + .setName('role') + .setDescription('The role to map the emoji to') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => subcommand + .setName('remove') + .setDescription('Remove a react-role from the selected message') + .setHandler(requireAuth(cmdRoleRemove)) + .addStringOption(option => option + .setName('emoji') + .setDescription('The emoji mapping to remove') + .setRequired(false) + ) + .addRoleOption(option => option + .setName('role') + .setDescription('The role mapping to remove') + .setRequired(false) + ) + ) + .addSubcommand(subcommand => subcommand + .setName('remove-all') + .setDescription('Remove ALL react-roles from the selected message') + .setHandler(requireAuth(cmdRoleRemoveAll)) + ) + ) + .addCommand(command => command + .setName('permission') + .setDescription('Manage who is allowed to configure the bot') + .addSubcommand(subcommand => subcommand + .setName('add') + .setDescription('Add a role that can configure the bot') + .setHandler(requireAuth(cmdPermAdd)) + .addRoleOption(option => option + .setName('role') + .setDescription('The role that will be able to configure the bot') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => subcommand + .setName('remove') + .setDescription('Remove a role that can configure the bot') + .setHandler(requireAuth(cmdPermRemove)) + .addRoleOption(option => option + .setName('role') + .setDescription( + 'The role that will no longer be able to configure the bot' + ) + .setRequired(true) + ) + ) + ) + .addCommand(command => command + .setName('mutex') + .setDescription('Manage mutually exclusive react roles') + .addSubcommand(subcommand => subcommand + .setName('add') + .setDescription('Make two react roles mutually exclusive for this server') + .setHandler(requireAuth(cmdMutexAdd)) + .addRoleOption(option => option + .setName('role1') + .setDescription('The first mutually exclusive role') + .setRequired(true) + ) + .addRoleOption(option => option + .setName('role2') + .setDescription('The second mutually exclusive role') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => subcommand + .setName('remove') + .setDescription( + 'Remove the mutually exclusive restriction on two react roles' + ) + .setHandler(requireAuth(cmdMutexRemove)) + .addRoleOption(option => option + .setName('role1') + .setDescription('The first mutually exclusive role') + .setRequired(true) + ) + .addRoleOption(option => option + .setName('role2') + .setDescription('The second mutually exclusive role') + .setRequired(true) + ) + ) + ) + .addCommand(command => command + .setName('reset-everything') + .setDescription('Deletes ALL configuration for this server') + .setHandler(requireAuth(cmdReset)) + ); + +/** + * Middleware for command handlers that ensures the user initiating an + * interaction has permission to do so, and short-circuits if they don't. + */ +function requireAuth(handler) { + return async function(interaction) { + const member = await interaction.member.fetch(); // Ensures cache + + if (member.permissions.has(Discord.Permissions.FLAGS.ADMINISTRATOR)) { + return handler(interaction); + } + + const allowedRoles = await database.getAllowedRoles(interaction.guild.id); + if (allowedRoles.some(role => member.roles.cache.has(role))) { + return handler(interaction); + } + + return ephemReply(interaction, "You don't have permission to use that!"); + } +} + +/** + * Replies with info about this bot, including a link to the source code to be + * compliant with the AGPLv3 this bot is licensed under. + */ +async function cmdInfo(interaction) { + const stats = await database.getMetaStats(); + return interaction.reply(asLines([ + info.description, + `${bold('Running version:')} ${info.version}`, + `${bold('Source code:')} ${info.homepage}`, + '', + codeBlock(asLines([ + 'Stats For Nerds:', + ` - Servers bot is active in: ${stats.guilds}`, + ` - Reaction role mappings: ${stats.roles}`, + ` - Total role assignments: ${stats.assignments}`, + ])), + ])); +} + +/** + * Saves a user's selected message for subsequent actions. + */ +async function cmdSelect(interaction) { + const message = _selectCommon(interaction, SELECTED_MESSAGE_CACHE); + return ephemReply(interaction, `Selected message: ${message.url}`); +} + +/** + * Saves a user's selected clone target message for subsequent clone. + */ +async function cmdSelectCopy(interaction) { + const message = _selectCommon(interaction, CLONE_MESSAGE_CACHE); + return ephemReply(interaction, `Selected copy target: ${message.url}`); +} + +// Common logic between cmdSelect and cmdSelectClone +function _selectCommon(interaction, cache) { + const user = interaction.user; + const message = interaction.options.getMessage('message', true); + + // Always clear selected message first, just to be safe and consistent. + cache.del(user.id); + cache.set(user.id, message); + + return message; +} + +/** + * An alternative way to select messages using slash commands instead of context + * menus, since Discord mobile does not currently support context menus. + */ +async function cmdSelectMobile(interaction) { + const url = await _selectCloneCommon(interaction, SELECTED_MESSAGE_CACHE); + if (url) { + return ephemReply(interaction, `Selected message: ${url}`); + } +} + +/** + * An alternative way to select clone target messages using slash commands + * instead of context menus. + */ +async function cmdSelectCopyMobile(interaction) { + const url = await _selectCloneCommon(interaction, CLONE_MESSAGE_CACHE); + if (url) { + return ephemReply(interaction, `Selected copy target: ${url}`); + } +} + +// Common logic between cmdSelectMobile and cmdSelectCloneMobile +async function _selectCloneCommon(interaction, cache) { + function reportInvalid(err) { + logger.warn('Failed to select message by URL', err); + return ephemReply(interaction, 'Invalid message link!'); + } + + const url = interaction.options.getString('message-url', true); + const match = url.match(/^https:\/\/discord\.com\/channels\/\d+\/(\d+)\/(\d+)$/); + + if (!match) { + return reportInvalid(); + } + + const channel_id = match[1]; + const message_id = match[2]; + + let message; + try { + const channel = await interaction.guild.channels.fetch(channel_id); + message = await channel?.messages.fetch(message_id); + } catch (err) { + return reportInvalid(err); + } + + cache.del(interaction.user.id); + cache.set(interaction.user.id, message); + + return url; +} + +/** + * Shows a user their currently selected message. + */ +async function cmdSelected(interaction) { + const message = SELECTED_MESSAGE_CACHE.get(interaction.user.id); + return ephemReply(interaction, message + ? `Currently selected: ${message.url}` + : 'No message currently selected' + ); +} + +/** + * Shows a user their currently selected copy target message. + */ +async function cmdSelectedCopy(interaction) { + const message = CLONE_MESSAGE_CACHE.get(interaction.user.id); + return ephemReply(interaction, message + ? `Current copy target: ${message.url}` + : 'No copy target message currently selected' + ); +} + +/** + * Map an emoji reaction with a role on the currently selected message. + */ +async function cmdRoleAdd(interaction) { + const emoji = Options.getEmoji(interaction, 'emoji', true); + const role = interaction.options.getRole('role', true); + let message = SELECTED_MESSAGE_CACHE.get(interaction.user.id); + + if (!message) { + return ephemReply(interaction, 'No message selected! Select a message first.'); + } + + if (!emoji) { + return ephemReply(interaction, 'Not a valid emoji!'); + } + + message = await message.fetch(); + + // Prevent someone from modifying a server from outside the server. + if (interaction.guild !== message.guild || interaction.guild !== role.guild) { + return ephemReply(interaction, unindent(` + Message and Role need to be in the same Server this command + was issued from! + `)); + } + + return database.transaction(async trx => { + const emoji_key = emojiToKey(emoji); + + const mapping = await database.getRoleReactMap(message.id, trx); + const mutex_roles = await database.getMutexRoles({ + guild_id: interaction.guild.id, + role_id: role.id, + }, trx); + if (mutex_roles.find(mrole_id => mapping.has(emoji_key, mrole_id))) { + const conflicting = mutex_roles.filter( + mrole_id => mapping.has(emoji_key, mrole_id) + ); + return await ephemReply(interaction, unindent(` + Cannot add emoji-role mapping because it conflicts with mutually + exclusive roles mapped to this emoji! Conflicting roles: + ${conflicting.map(mrole_id => roleMention(mrole_id)).join(', ')} + `)); + } + + const db_data = { + guild_id: interaction.guild.id, + message_id: message.id, + emoji_id: emoji_key, + role_id: role.id, + }; + + try { + await database.addRoleReact(db_data, trx); + } catch (err) { + logger.error(`Database failed to create ${stringify(db_data)}`, err); + await ephemReply(interaction, 'Something went wrong. Try again?'); + rethrowHandled(err); + } + + try { + await message.react(emoji); + } catch (err) { + logger.warn(`Could not add ${stringify(emoji)} to ${stringify(message)}`, err); + await ephemReply(interaction, + 'I could not react to your selected message. Do I have the right permissions?' + ); + rethrowHandled(err); + } + + let response = `Mapped ${emoji} to ${role} on ${stringify(message)}`; + const also_mapped = entries(mapping) + .filter(([eid, rid]) => eid !== emoji_key && rid === role.id) + .map(([eid, _]) => message.reactions.resolve(eid).emoji); + if (also_mapped.length > 0) { + response += `\nNote: also mapped to ${also_mapped.join(' ')}`; + } + return ephemReply(interaction, response); + }); +} + +/** + * Removes an emoji mapping from the currently selected message. + */ +async function cmdRoleRemove(interaction) { + const emoji = Options.getEmoji(interaction, 'emoji', false); + const role = interaction.options.getRole('role', false); + let message = SELECTED_MESSAGE_CACHE.get(interaction.user.id); + + if (!message) { + return ephemReply(interaction, 'No message selected! Select a message first.'); + } + + if (!emoji && !role) { + return ephemReply(interaction, 'You must specify an emoji or a role (or both)!'); + } + + message = await message.fetch(); + + return database.transaction(async trx => { + // Need to reply to keep the interaction token alive while we delete + await ephemReply(interaction, 'Removing, this may take a moment...'); + + let map_before; + const db_data = { + message_id: message.id, + emoji_id: emoji ? emojiToKey(emoji) : undefined, + role_id: role?.id, + }; + try { + map_before = await database.getRoleReactMap(message.id, trx); + // Intentionally NOT removing roles from users who currently have them + await database.removeRoleReact(db_data, trx); + } catch (err) { + logger.error(`Database failed to remove mappings ${stringify(db_data)}`, err); + await ephemEdit(interaction, 'Something went wrong. Try again?'); + rethrowHandled(err); + } + + let map_after; + try { + map_after = await database.getRoleReactMap(message.id, trx); + await Promise.all(message.reactions.cache + .filter((_, msg_emoji) => !map_after.has(msg_emoji)) + .map(react => react.remove())); + } catch (err) { + logger.error(`Could not remove reacts from ${stringify(message)}`, err); + await ephemEdit(interaction, + 'I could not remove the react(s). Do I have the right permissions?' + ); + rethrowHandled(err); + } + + entries(map_after).forEach( + ([emoji_key, role_id]) => map_before.delete(emoji_key, role_id) + ); + const removed_pairs = entries(map_before); + + return ephemEdit(interaction, removed_pairs.length === 0 + ? 'Selected message has no mappings for the given emoji and/or role!' + : `Removed mappings:\n${ + removed_pairs.map(([emoji_id, role_id]) => { + const emoji_str = interaction.client.emojis.resolve(emoji_id) ?? emoji_id; + return `${emoji_str} -> ${roleMention(role_id)}` + }).join('\n') + }\nFrom ${stringify(message)}` + ); + }); +} + +/** + * Removes all emoji mappings from the currently selected message. + * This remains a separate function to avoid accidentally nuking a message. + */ +async function cmdRoleRemoveAll(interaction) { + let message = SELECTED_MESSAGE_CACHE.get(interaction.user.id); + + if (!message) { + return ephemReply(interaction, 'No message selected! Select a message first.'); + } + + message = await message.fetch(); + + return database.transaction(async trx => { + let removed; + try { + removed = await database.removeAllRoleReacts(message.id, trx); + await message.reactions.removeAll(); + } catch (err) { + logger.error(`Could not remove all reacts from ${stringify(message)}`, err); + await ephemReply(interaction, + 'I could not remove the reacts. Do I have the right permissions?' + ); + rethrowHandled(err); + } + + return ephemReply(interaction, + removed + ? `Removed all react roles from ${stringify(message)}` + : `Selected message does not have any role reactions! ${message.url}` + ); + }); +} + +/** + * Adds a role that can configure this bot's settings for a guild. + */ +async function cmdPermAdd(interaction) { + const role = interaction.options.getRole('role', true); + + if (interaction.guild !== role.guild) { + return ephemReply(interaction, 'Role must belong to this guild!'); + } + + try { + await database.addAllowedRole({ + guild_id: interaction.guild.id, + role_id: role.id, + }); + } catch (err) { + if (err.message.includes('UNIQUE constraint failed')) { + return ephemReply(interaction, `${role} can already configure me!`); + } else { + logger.error(`Could not add permission for ${stringify(role)}`, err); + return ephemReply(interaction, 'Something went wrong. Try again?'); + } + } + + return ephemReply(interaction, `${role} can now configure me`); +} + +/** + * Removes a role from being able to configure this bot's settings for a guild. + * A role can remove itself, which is dumb, but whatever. + */ +async function cmdPermRemove(interaction) { + const role = interaction.options.getRole('role', true); + + if (interaction.guild !== role.guild) { + return ephemReply(interaction, 'Role must belong to this guild!'); + } + + let removed; + try { + removed = await database.removeAllowedRole({ + guild_id: interaction.guild.id, + role_id: role.id, + }); + } catch (err) { + logger.error(`Could not remove permission for ${stringify(role)}`, err); + return ephemReply(interaction, 'Something went wrong. Try again?'); + } + + return ephemReply(interaction, + `${role} ${ + removed === 1 ? 'is no longer' : 'was already not' + } allowed to configure me` + ); +} + +/** + * Make two roles mutually exclusive for a guild. + * This is for the whole guild, not just a single message. + */ +async function cmdMutexAdd(interaction) { + const role1 = interaction.options.getRole('role1', true); + const role2 = interaction.options.getRole('role2', true); + + if (interaction.guild !== role1.guild || interaction.guild != role2.guild) { + return ephemReply(interaction, 'Roles must belong to this guild!'); + } + + if (role1 === role2) { + return ephemReply(interaction, + 'Cannot make a role mutually exclusive with itself!' + ); + } + + try { + await database.addMutexRole({ + guild_id: interaction.guild.id, + role_id_1: role1.id, + role_id_2: role2.id, + }); + } catch (err) { + if (err.message.includes('UNIQUE constraint failed')) { + return ephemReply(interaction, + `Roles ${role1} and ${role2} are already mutually exclusive!` + ); + } else { + logger.error(unindent(` + Could not make ${stringify(role1)} and ${stringify(role2)} + mutually exclusive + `), err); + return ephemReply(interaction, 'Something went wrong. Try again?'); + } + } + + return ephemReply(interaction, + `Roles ${role1} and ${role2} are now mutually exclusive in this server` + ); +} + +/** + * Removes the mutually exclusive restriction for two roles in a guild. + */ +async function cmdMutexRemove(interaction) { + const role1 = interaction.options.getRole('role1', true); + const role2 = interaction.options.getRole('role2', true); + + if (interaction.guild !== role1.guild || interaction.guild != role2.guild) { + return ephemReply(interaction, 'Roles must belong to this guild!'); + } + + let removed; + try { + removed = await database.removeMutexRole({ + guild_id: interaction.guild.id, + role_id_1: role1.id, + role_id_2: role2.id, + }); + } catch (err) { + logger.error( + `Could not remove mutex for ${stringify(role1)} and ${stringify(role2)}`, + err + ); + return ephemReply(interaction, 'Something went wrong. Try again?'); + } + + return ephemReply(interaction, + `Roles ${role1} and ${role2} ${ + removed === 1 ? 'are no longer' : 'were already not' + } mutually exclusive` + ); +} + +/** + * Removes all data for a guild. This includes role react mappings, allowed + * configuration roles, and mutually exclusive constraints on roles. + */ +async function cmdReset(interaction) { + try { + await database.clearGuildInfo(interaction.guild.id); + } catch (err) { + logger.error(`Failed to clear data for ${stringify(interaction.guild)}`, err); + return ephemReply(interaction, 'Something went wrong. Try again?'); + } + + return ephemReply(interaction, 'Deleted all configuration for this guild!'); +} + +/** + * Copies role-react mappings from the selected message to the copy target + * message. This requires both a message and a copy target to be selected. + */ +async function cmdCopyMappings(interaction) { + let msg_from = SELECTED_MESSAGE_CACHE.get(interaction.user.id); + let msg_copy = CLONE_MESSAGE_CACHE.get(interaction.user.id); + + if (!msg_from) { + return ephemReply(interaction, + 'No message selected! Select a message first.' + ); + } + + if (!msg_copy) { + return ephemReply(interaction, + 'No copy target selected! Select a copy target message first.' + ); + } + + if (msg_from === msg_copy) { + return ephemReply(interaction, 'Cannot copy a message to itself!'); + } + + msg_from = await msg_from.fetch(); + msg_copy = await msg_copy.fetch(); + + // Prevent modifying a server from outside a server + const guild = interaction.guild; + if (guild !== msg_from.guild || guild !== msg_copy.guild) { + return ephemReply(interaction, unindent(` + Source and target messages need to be in the same Server this + command was issued from! + `)); + } + + // Need to reply to keep the interaction token alive while we copy + await ephemReply(interaction, 'Copying, this may take a moment...'); + + return database.transaction(async trx => { + const mapping = await database.getRoleReactMap(msg_from.id, trx); + for await (const [emoji_id, role_id] of entries(mapping)) { + try { + await database.addRoleReact({ + guild_id: guild.id, + message_id: msg_copy.id, + emoji_id: emoji_id, + role_id: role_id, + }, trx); + } catch (err) { + logger.error('Failed to copy roles', err); + await ephemEdit(interaction, 'Something went wrong. Try again?'); + rethrowHandled(err); + } + + try { + await msg_copy.react(emoji_id); + } catch (err) { + logger.warn(`Cannot copy roles to ${msg_copy.url}`); + await ephemEdit(interaction, unindent(` + Could not add reacts to the target message. Do I have the + right permissions? + `)); + rethrowHandled(err); + } + } + + return ephemEdit(interaction, mapping.size === 0 + ? 'Selected message has no mappings!' + : 'Copied react-role mappings.\n' + + `Source: ${msg_from.url}\n` + + `Target: ${msg_copy.url}\n` + + 'You can delete the original message now.' + ); + }); +} + +module.exports = REGISTRY; + diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..8806b50 --- /dev/null +++ b/src/database.js @@ -0,0 +1,419 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const knexfile = require('./knexfile'); +const knex = require('knex')(knexfile[process.env.NODE_ENV || 'development']); +const lodash = require('lodash'); +const MultiMap = require('multimap'); + +const logger = require('./logger'); +const { + isDiscordId, + isEmojiStr, + stringify, +} = require('./util'); + +const META = 'meta'; +const MUTEX = 'mutex'; +const PERMS = 'perms'; +const REACTS = 'reacts'; + +// Poor man's enum +const DISCORD_ASSERT = 1; +const EMOJI_ASSERT = 2; + +/** + * Helper asserting database arguments look the way we want them to. + * + * SQLite3 has pretty lax enforcement of its constraints, so we need to do a + * little extra work to ensure we're not putting garbage in the database. + * + * Also, since we want to constrain arguments everywhere we would assert on + * them, just do the argument selection here too. + */ +function _pickAndAssertFields(args, asserts) { + lodash.toPairs(asserts).forEach(([ key, type ]) => { + const value = args[key]; + if (type === DISCORD_ASSERT && !isDiscordId(value)) { + throw Error(`${key} invalid Discord ID: ${value}`); + } + if (type === EMOJI_ASSERT && !isDiscordId(value) && !isEmojiStr(value)) { + throw Error(`${key} invalid Emoji key: ${value}`); + } + }); + + const args_we_need = lodash.pick(args, lodash.keys(asserts)); + + // Extra arguments shouldn't happen in normal operation, but since we pick a + // subset of arguments anyway, just warn about them. + if (!lodash.isEqual(args, args_we_need)) { + const extras = lodash.omit(args, lodash.keys(asserts)); + logger.warn(`Extra database query arguments: ${stringify(extras)}`); + } + + return args_we_need; +} + +/** + * Simple assert to ensure value is a valid Discord ID. + */ +function _assertDiscordId(value) { + if (!isDiscordId(value)) { + throw Error(`Invalid Discord ID: ${value}`); + } +} + +/** + * Simple assert to ensure value is a valid Emoji string or Discord ID. + */ +function _assertEmojiKey(value) { + if (!isDiscordId(value) && !isEmojiStr(value)) { + throw Error(`Invalid Emoji key: ${value}`); + } +} + +/** + * A pass-through for knex.transaction(...) that suppresses errors we have + * already handled. + * + * Knex always returns a rejected promist from a rolled back transaction, and + * rolls back transactions when Errors are thrown from the transaction + * block. + * + * We don't have a great way to differentiate between database Errors and + * Discord Errors based on their prototype. The only way is to wrap each method + * in their own try-catch, so that makes Knex' catch-all rejection behavior + * problematic. This is our solution. + */ +function transaction(func) { + return knex.transaction(func).catch(err => { + if (!err.handled) throw err; + logger.debug('Suppressing handled error within transaction'); + }); +} + +/** + * Marks an Error as "handled" then rethrows it. + * See {@link transaction} for why this is needed. + */ + function rethrowHandled(err) { + if (err instanceof Error) { + err.handled = true; + } + throw err; +} + +/** + * Adds an emoji->role mapping for the given message. If the emoji is already + * mapped to a role on this message, that mapping is replaced. + * + * This is essentially an upsert, but "upsert" is a stupid word, so "add" it is. + */ +function addRoleReact(args, trx) { + const fields = _pickAndAssertFields(args, { + guild_id: DISCORD_ASSERT, + message_id: DISCORD_ASSERT, + emoji_id: EMOJI_ASSERT, + role_id: DISCORD_ASSERT, + }); + + return (trx ? trx(REACTS) : knex(REACTS)) + .insert(fields) + .catch(err => { + if (err.message.includes('UNIQUE constraint failed')) { + return (trx ? trx(REACTS) : knex(REACTS)) + .where(lodash.pick(fields, ['message_id', 'emoji_id', 'role_id'])) + .update({ role_id: fields.role_id }); + } else { + throw err; + } + }); +} + +/** + * Removes an emoji->role mapping for the given message. + * At least one of emoji_id or role_id must be provided. Mappings will be + * removed based on the data provided (e.g. if an emoji is provided, all + * mappings for that emoji are removed). + */ +function removeRoleReact(args, trx) { + const fields = {}; + + const message_id = args.message_id; + const emoji_id = args.emoji_id; + const role_id = args.role_id; + + _assertDiscordId(message_id); + fields.message_id = message_id; + + if (!emoji_id && !role_id) { + throw new Error('Need one of emoji_id or role_id'); + } + + if (emoji_id) { + _assertEmojiKey(emoji_id); + fields.emoji_id = emoji_id; + } + + if (role_id) { + _assertDiscordId(role_id); + fields.role_id = role_id; + } + + return (trx ? trx(REACTS) : knex(REACTS)).where(fields).del(); +} + +/** + * Removes all emoji->role mappings for the given message. + */ +function removeAllRoleReacts(message_id, trx) { + _assertDiscordId(message_id); + return (trx ? trx(REACTS) : knex(REACTS)).where('message_id', message_id).del(); +} + +/** + * Returns the roles as an Array for the given emoji on the given message. + */ +function getRoleReacts(args) { + const fields = _pickAndAssertFields(args, { + message_id: DISCORD_ASSERT, + emoji_id: EMOJI_ASSERT, + }); + + return knex(REACTS) + .select('role_id') + .where(fields) + .then(results => results.map(row => row.role_id)); +} + +/** + * Returns the emoji->role mapping for the given message as a MultiMap. + */ +function getRoleReactMap(message_id, trx) { + _assertDiscordId(message_id); + return (trx ? trx(REACTS) : knex(REACTS)) + .select(['emoji_id', 'role_id']) + .where('message_id', message_id) + .then(rows => new MultiMap( + rows.map(({emoji_id, role_id}) => [emoji_id, role_id]) + )); +} + +/** + * Returns whether the given message has any role react mappings on it. + */ +function isRoleReactMessage(message_id) { + _assertDiscordId(message_id); + return knex(REACTS) + .select('message_id') + .where('message_id', message_id) + .first() + .then(result => !!result); +} + +function getRoleReactMessages(guild_id) { + _assertDiscordId(guild_id); + return knex(REACTS) + .distinct('message_id') + .where('guild_id', guild_id) + .then(rows => rows.map(row => row.message_id)); +} + +/** + * Deletes all the data stored for the given guild. + */ +function clearGuildInfo(guild_id) { + _assertDiscordId(guild_id); + return Promise.all([REACTS, PERMS, MUTEX].map(table => + knex(table).where('guild_id', guild_id).del() + )); +} + +/** + * Increments the meta table's role assignment counter. + */ +function incrementAssignCounter(num) { + return knex(META).increment('assignments', num ?? 1); +} + +/** + * Returns the following object of meta stats about the bot: + * - guilds: + * - roles: + * - assignments: + */ +function getMetaStats() { + return Promise.all([ + knex(REACTS).countDistinct('guild_id as count').first(), + knex(REACTS).count().first(), + knex(META).select('assignments').first() + ]).then(([res1, res2, res3]) => { + return { + guilds: res1['count'], + roles: res2['count(*)'], + assignments: res3.assignments + }; + }); +} + +/** + * Adds a new role that's allowed to configure this bot for the given guild. + */ +function addAllowedRole(args) { + const fields = _pickAndAssertFields(args, { + guild_id: DISCORD_ASSERT, + role_id: DISCORD_ASSERT, + }); + + return knex(PERMS).insert(fields); +} + +/** + * Removes a role from being allowed to configure this bot for the given guild. + */ +function removeAllowedRole(args) { + const fields = _pickAndAssertFields(args, { + guild_id: DISCORD_ASSERT, + role_id: DISCORD_ASSERT, + }); + + return knex(PERMS).where(fields).del(); +} + +/** + * Returns the list of roles that can configure this bot for the given guild. + */ +function getAllowedRoles(guild_id) { + _assertDiscordId(guild_id); + return knex(PERMS) + .select('role_id') + .where({ guild_id: guild_id }) + .then(roleArray => roleArray.map(elem => elem.role_id)); +} + +/** + * Creates a mutually exclusive rule for two roles in the given guild. + * role_id_1 and role_id_2 are interchangable, so if there's already a record + * for roleA and roleB, attempting to add a record for roleB and roleA will + * throw a unique constraint violation exception. + */ +function addMutexRole(args) { + const fields = _pickAndAssertFields(args, { + guild_id: DISCORD_ASSERT, + role_id_1: DISCORD_ASSERT, + role_id_2: DISCORD_ASSERT, + }); + + // Need to try role 1 and role 2 in reverse order too + let flipped = lodash.pick(args, ['guild_id']); + flipped.role_id_1 = fields.role_id_2; + flipped.role_id_2 = fields.role_id_1; + + return knex(MUTEX) + .first() + .where(fields) + .then(record => { + // If record exists, insert it again to cause a unique constraint + // exception. If not, try to insert the fields in reverse order + // (which will also cause a unique constraint if it exists). + let version = record ? fields : flipped; + return knex(MUTEX).insert(version); + }); +} + +/** + * Removes the mutually exclusive rule for the two roles in the given guild. + * role_id_1 and role_id_2 are interchangable here the same way they are in + * addMutexRole. + */ +function removeMutexRole(args) { + const fields = _pickAndAssertFields(args, { + guild_id: DISCORD_ASSERT, + role_id_1: DISCORD_ASSERT, + role_id_2: DISCORD_ASSERT, + }); + const flipped = lodash.pick(fields, ['guild_id']); + flipped.role_id_1 = fields.role_id_2; + flipped.role_id_2 = fields.role_id_1; + + // We can just try to delete with roles in both orders. + return Promise.all([ + knex(MUTEX).where(fields).del(), + knex(MUTEX).where(flipped).del() + ]).then(([count1, count2]) => ((count1 ?? 0) + (count2 ?? 0))); +} + +/** + * Returns the list of roles that are mutually exclusive with the given role, + * for the given guild. If no roles are mutually exclusive, an empty array is + * returned. + */ +function getMutexRoles(args, trx) { + const fields = _pickAndAssertFields(args, { + guild_id: DISCORD_ASSERT, + role_id: DISCORD_ASSERT, + }); + + // Roles could be added in either order, so fetch with both orders and + // combine the results. + const builder = trx ? trx : knex; + return Promise.all([ + builder(MUTEX).select('role_id_1').where({ + guild_id: fields.guild_id, + role_id_2: fields.role_id + }), + builder(MUTEX).select('role_id_2').where({ + guild_id: fields.guild_id, + role_id_1: fields.role_id + }) + ]).then(([res1, res2]) => [ + ...res1.map(row => row.role_id_1), + ...res2.map(row => row.role_id_2) + ]); +} + +/** + * Takes a list of roles and returns the list of emojis associated with them. + * This is mostly so we can remove reacts in bulk. + * XXX: It might make sense to return this as key-value pairs in the future, + * instead of just an array. + */ +function getMutexEmojis(roles) { + if (!Array.isArray(roles)) { + throw Error('roles must be an Array of Discord IDs'); + } + roles.forEach(_assertDiscordId); + + return knex(REACTS) + .select('emoji_id') + .whereIn('role_id', roles) + .then(res => res.map(elem => elem.emoji_id)); +} + +module.exports = { + transaction, + rethrowHandled, + addRoleReact, + removeRoleReact, + removeAllRoleReacts, + getRoleReacts, + getRoleReactMap, + isRoleReactMessage, + getRoleReactMessages, + clearGuildInfo, + incrementAssignCounter, + getMetaStats, + addAllowedRole, + removeAllowedRole, + getAllowedRoles, + addMutexRole, + removeMutexRole, + getMutexRoles, + getMutexEmojis +}; + diff --git a/src/events.js b/src/events.js new file mode 100644 index 0000000..e70ad6d --- /dev/null +++ b/src/events.js @@ -0,0 +1,295 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const lodash = require('lodash'); +const Perms = require('discord.js').Permissions.FLAGS; + +const commands = require('./commands'); +const database = require('./database'); +const logger = require('./logger'); +const { + detail, + emojiToKey, + stringify, + unindent, +} = require('./util'); + +const REQUIRED_PERMISSIONS = Object.freeze({ + [Perms.ADD_REACTIONS]: 'Add Reactions', + [Perms.MANAGE_MESSAGES]: 'Manage Messages', + [Perms.MANAGE_ROLES]: 'Manage Roles', + [Perms.READ_MESSAGE_HISTORY]: 'Read Message History', + [Perms.USE_EXTERNAL_EMOJIS]: 'Use External Emojis', + [Perms.VIEW_CHANNEL]: 'Read Text Channels & See Voice Channels', +}); + +/** + * Event handler for when the bot joins a new guild. + * DMs the guild owner with some basic instructions, including any missing + * required permissions. + */ +async function onGuildJoin(guild) { + logger.info(`Joined ${stringify(guild)}`); + + let text = unindent(` + Hi there! My role needs to be ordered above any role you want me to + assign. You are getting this message because you are the server owner, + but anybody with Administrator permissions or an allowed role can + configure me. + `); + + // This bot probably shouldn't be given the admin permission, but if we have + // it then the other ones don't matter. + // These permissions can also be inherited from the server's @everyone + // permissions. + const client_member = await guild.members.fetch(guild.client.user); + const missing_perms = Object.entries(REQUIRED_PERMISSIONS) + .filter(([ perm, name ]) => !client_member.permissions.has(perm, true)) + .map(([ perm, name ]) => name); + + if (missing_perms.length > 0) { + text += '\n\n'; + text += unindent(` + Also, I am missing the following permissions. Without them, I + probably won't work right: + `); + text += '\n'; + text += missing_perms.map(name => `- ${name}`).join('\n'); + + logger.info(`${stringify(guild)} missing permissions: ${missing_perms}`); + } + + const guild_owner = await guild.fetchOwner(); + const owner_dm = await guild_owner.createDM(); + return owner_dm.send(text); +} + +/** + * Event handler for when the bot leaves (or is kicked from) a guild. + * Deletes all data associated with that guild. + */ +async function onGuildLeave(guild) { + try { + await database.clearGuildInfo(guild.id) + logger.info(`Left ${stringify(guild)}, deleted all related data`); + } catch (err) { + logger.error(`Left ${stringify(guild)} but failed to delete data!`, err); + } +} + +/** + * Event handler for receiving some kind of interaction. + * Logs the interaction and passes it on to the command handler. + */ +async function onInteraction(interaction) { + logger.info(`Received ${detail(interaction)}`); + + try { + await commands.execute(interaction); + } catch (err) { + logger.error(`${detail(interaction)} error fell through:`, err); + } +} + +/** + * Event handler for when messages are deleted in bulk. + * Removes any react roles configured for the deleted messages. + */ +async function onMessageBulkDelete(messages) { + for (const message of messages.values()) { + console.log(message.id); + onMessageDelete(message); + } +} + +/** + * Event handler for when a message is deleted. + * Removes any react-roles configured for the deleted message. + */ +async function onMessageDelete(message) { + try { + const removed = await database.removeAllRoleReacts(message.id); + if (removed) { + logger.info(`Deleted ${stringify(message)}, removed ${removed} mappings`); + } + } catch (err) { + logger.error(`Deleted ${stringify(message)} but failed to clear records`, err); + } +} + +/** + * Event handler for when a reaction is added to a message. + * Checks if the message has any reaction roles configured for the given emoji. + * If so, adds that role (or roles) to the user who added the reaction. + * Removes any reaction that doesn't correspond to a role. + */ +async function onReactionAdd(reaction, react_user) { + logger.debug(`Added ${detail(reaction)}`); + + // Ignore our own reactions + if (react_user === react_user.client.user) { + return; + } + + // Ignore reactions on non-role-react posts + if (!await database.isRoleReactMessage(reaction.message.id)) { + return; + } + + const role_ids = await database.getRoleReacts({ + message_id: reaction.message.id, + emoji_id: emojiToKey(reaction.emoji), + }); + + // Someone added an emoji that isn't mapped to a role + if (role_ids.length === 0) { + return reaction.remove(); + } + + const member = await reaction.message.guild.members.fetch(react_user.id); + + // Remove mutually exclusive roles from user + const mutex_roles = lodash.flatMap( + await Promise.all(role_ids.map(role_id => database.getMutexRoles({ + guild_id: reaction.message.guild.id, + role_id: role_id, + }))) + ); + try { + await member.roles.remove(mutex_roles, 'Role bot removal (mutex)'); + await member.roles.add(role_ids, 'Role bot assignment'); + } catch (err) { + logger.warn(`Failed to update roles on ${stringify(react_user)}`, err); + } + + // Remove associated mutually exclusive emoji reactions + const mutex_emojis = await database.getMutexEmojis(mutex_roles); + for await (const emoji of mutex_emojis) { + const mutex_reaction = reaction.message.reactions.resolve(emoji); + if (mutex_reaction) { + mutex_reaction.users.remove(react_user); + } + } + + // Track assignment number for fun + await database.incrementAssignCounter(role_ids.length); + + logger.info(`Added Roles ${stringify(role_ids)} to ${stringify(react_user)}`); +} + +/** + * Event handler for when a reaction is removed from a message. + * Checks if the message has any reaction roles configured for the given emoji. + * If so, removes that role (or roles) from the user whose reaction was removed. + * Also re-adds the bot's reaction if it is removed while a react-role is active. + * + * NOTE: + * This is only fired when a single reaction is removed, either by clicking on + * an emoji or through the message's "reactions" context menu. It is NOT fired + * when a bot removes all reactions (Discord uses a seprate event for that). + * + * The user this handler receives is the user whose reaction was removed. + * Discord does not tell us who actually removed that user's reaction. We can't + * tell when an admin removes a reaction instead of the user themselves, so this + * handler will always just remove the role from the user. + */ +async function onReactionRemove(reaction, react_user) { + logger.debug(`Removed ${detail(reaction)}`); + + // TODO Maybe be a little smarter about how we remove roles when multiple + // emojis map to that role. Currently we remove the roll when a single emoji + // mapped to it is removed. Maybe we should wait until all emojis mapped to + // it are removed? + + const emoji = reaction.emoji; + + const role_ids = await database.getRoleReacts({ + message_id: reaction.message.id, + emoji_id: emojiToKey(emoji), + }); + + // Ignore reactions on non-role-react posts + if (role_ids.length === 0) { + return; + } + + if (react_user === react_user.client.user) { + logger.info(`Replacing removed bot reaction ${stringify(emoji)}`); + return reaction.message.react(emoji); + } + + try { + const member = await reaction.message.guild.members.fetch(react_user.id); + await member.roles.remove(role_ids, 'Role bot removal'); + logger.info(`Removed Roles ${stringify(role_ids)} from ${stringify(react_user)}`); + } catch (err) { + logger.error( + `Failed to remove Roles ${stringify(role_ids)} from ${stringify(react_user)}`, + err + ); + } +} + +/** + * Event handler for when the bot is logged in. + * + * Logs the bot user we logged in as. + * + * Pre-caches messages we have react role mappings on. This prevents an issue + * where the bot sometimes fails to pick up reacts when it first restarts. + */ +async function onReady(client) { + logger.info(`Logged in as ${client.user.tag} (${client.user.id})`); + logger.info('Precaching messages...'); + + // Despite message IDs being unique, we can only fetch a message by ID + // through a channel object, so we need to iterate over all channels and + // search each one for the messages we expect. + let numCached = 0; + await Promise.all(client.guilds.cache.map(async guild => { + const guild_message_ids = await database.getRoleReactMessages(guild.id); + let errors = {}; // Allows us to aggregate and report errors + + await Promise.all(guild.channels.cache.map(async channel => { + await Promise.all(guild_message_ids.map(async id => { + try { + if (await channel.messages?.fetch(id)) { + numCached++; + } + } catch (err) { + if (err.message.includes('Unknown Message')) { + return; // Expected when message isn't in this channel + } else { + errors[err.message] ??= 0; + errors[err.message]++; + } + } + })); + })); + + Object.entries(errors) + .filter(([msg, count]) => count) + .forEach(([msg, count]) => { + logger.warn(`Pre-cache ${stringify(guild)} - ${msg} x${count}`) + }); + })); + + logger.info(`Finished pre-cache (${numCached} messages)`); +} + +module.exports = { + onGuildJoin, + onGuildLeave, + onInteraction, + onMessageBulkDelete, + onMessageDelete, + onReactionAdd, + onReactionRemove, + onReady, +}; + diff --git a/src/knexfile.js b/src/knexfile.js new file mode 100644 index 0000000..5556d75 --- /dev/null +++ b/src/knexfile.js @@ -0,0 +1,42 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const path = require('path'); + +module.exports = { + development: { + client: 'sqlite3', + useNullAsDefault: true, + connection: { + filename: path.join(__dirname, '..', 'dev.sqlite3'), + }, + // Helps us catch hanging transactions in dev by locking up the database + // if we forget to commit anything. + pool: { + min: 1, + max: 1, + }, + }, + + testing: { + client: 'sqlite3', + useNullAsDefault: true, + connection: { + filename: ':memory:' + } + }, + + prod: { + client: 'sqlite3', + useNullAsDefault: true, + connection: { + filename: '/srv/discord/rolebot.sqlite3' + } + } +}; + diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..742f4f5 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,56 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const path = require('path'); +const Winston = require('winston'); + +const LOG_FILE_NAME = path.join(__dirname, '..', 'output.log'); +const IS_PROD = process.env.NODE_ENV === 'prod'; + + +const logger = Winston.createLogger(); + +const logFormat = Winston.format.combine( + Winston.format.timestamp(), + Winston.format.printf( ({ level, message, timestamp, stack, ...extra }) => { + // Winston appends the error message to the log message by default, even + // when stack traces are enabled, so we need to manually unappend it. + // https://github.com/winstonjs/winston/issues/1660?ts=4#issuecomment-569413211 + if (stack) { + const err = extra[Symbol.for('splat')][0]; + message = message.replace(` ${err.message}`, '') + `\n${stack}`; + } + return `${timestamp} [${level}]: ${message}`; + }), +); + +logger.add(new Winston.transports.File({ + filename: LOG_FILE_NAME, + format: logFormat, + level: IS_PROD ? 'info' : 'debug', +})); +logger.add(new Winston.transports.Console({ + format: Winston.format.combine( + Winston.format.colorize(), + logFormat, + ), + level: IS_PROD ? 'error' : 'debug', +})); + + +// Rolling our own unhandled exception and Promise rejection handlers, because +// Winston's built-in ones kind of suck. +function errStr(err) { + return err instanceof Error ? err.stack : err; +} +process.on('uncaughtExceptionMonitor', err => logger.error(errStr(err))); +process.on('unhandledRejection', + err => logger.error(`Unhandled Promise rejection: ${errStr(err)}`)); + + +module.exports = logger; diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..7c9bdf9 --- /dev/null +++ b/src/main.js @@ -0,0 +1,61 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const fs = require('fs'); + +const Discord = require('discord.js'); + +const events = require('./events'); +const logger = require('./logger'); + +const CONFIG = JSON.parse(fs.readFileSync( + process.argv[2] || '/etc/discord/ReactionRoleBot/config.json' +)); +const PACKAGE = require('../package.json'); + +// Everything operates on IDs, so we can safely rely on partials. +// This allows reaction events to fire for uncached messages. +const client = new Discord.Client({ + intents: [ + Discord.Intents.FLAGS.GUILDS, + Discord.Intents.FLAGS.GUILD_MEMBERS, + Discord.Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS, + Discord.Intents.FLAGS.GUILD_MESSAGES, + Discord.Intents.FLAGS.GUILD_MESSAGE_REACTIONS, + ], + partials: [ + // https://discordjs.guide/popular-topics/reactions.html#listening-for-reactions-on-old-messages + Discord.Constants.PartialTypes.MESSAGE, + Discord.Constants.PartialTypes.CHANNEL, + Discord.Constants.PartialTypes.REACTION, + Discord.Constants.PartialTypes.USER, + ], + presence: { + activities: [{ + name: `Version ${PACKAGE.version}`, + type: Discord.Constants.ActivityTypes.PLAYING, + }], + }, +}); + +const Events = Discord.Constants.Events; +client.on(Events.CLIENT_READY, events.onReady); +client.on(Events.GUILD_CREATE, events.onGuildJoin); +client.on(Events.GUILD_DELETE, events.onGuildLeave); +client.on(Events.INTERACTION_CREATE, events.onInteraction); +client.on(Events.MESSAGE_BULK_DELETE, events.onMessageBulkDelete); +client.on(Events.MESSAGE_DELETE, events.onMessageDelete); +client.on(Events.MESSAGE_REACTION_ADD, events.onReactionAdd); +client.on(Events.MESSAGE_REACTION_REMOVE, events.onReactionRemove); + + +client.login(CONFIG.token).catch(err => { + logger.error('Failed to log in!', err); + process.exit(1); +}); + diff --git a/src/migrations/20200225015300_db.js b/src/migrations/20200225015300_db.js new file mode 100644 index 0000000..6e70003 --- /dev/null +++ b/src/migrations/20200225015300_db.js @@ -0,0 +1,32 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const DISCORD_ID_MAX = 19; +const REACTS = 'reacts'; + +/** + * Create a table mapping a reaction emoji to a role on a message in a guild. + * + * Using a composite primary key of message_id and emoji_id doubles as a thing + * that prevents multiple database entries for the same react. + */ +exports.up = function(knex) { + return knex.schema.createTable(REACTS, table => { + table.string('guild_id', DISCORD_ID_MAX); + table.string('message_id', DISCORD_ID_MAX); + table.string('emoji_id', DISCORD_ID_MAX); + table.string('role_id', DISCORD_ID_MAX); + + table.primary(['message_id', 'emoji_id']); + }); +}; + +exports.down = function(knex) { + return knex.schema.dropTable(REACTS); +}; + diff --git a/src/migrations/20200814190000_meta.js b/src/migrations/20200814190000_meta.js new file mode 100644 index 0000000..aeb1f8c --- /dev/null +++ b/src/migrations/20200814190000_meta.js @@ -0,0 +1,26 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const META = 'meta'; + +/** + * Creates a table for counting how many total assignments there have been. + */ +exports.up = function(knex) { + return knex.schema.createTable(META, table => { + table.integer('assignments'); + }).then(() => { + // There will only ever be one row in this table so we make it here. + return knex(META).insert({ assignments: 0 }); + }); +}; + +exports.down = function(knex) { + return knex.schema.dropTable(META); +}; + diff --git a/src/migrations/20200815043000_perms.js b/src/migrations/20200815043000_perms.js new file mode 100644 index 0000000..9f6cf5e --- /dev/null +++ b/src/migrations/20200815043000_perms.js @@ -0,0 +1,28 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const DISCORD_ID_MAX = 19; +const PERMS = 'perms'; + +/** + * Creates a table that maps a role to a guild, so that role can be allowed to + * configure the bot in the guild. + */ +exports.up = function(knex) { + return knex.schema.createTable(PERMS, table => { + table.string('guild_id', DISCORD_ID_MAX); + table.string('role_id', DISCORD_ID_MAX); + + table.primary(['guild_id', 'role_id']); + }); +}; + +exports.down = function(knex) { + return knex.schema.dropTable(PERMS); +}; + diff --git a/src/migrations/20200818203000_mutex.js b/src/migrations/20200818203000_mutex.js new file mode 100644 index 0000000..bc71ad5 --- /dev/null +++ b/src/migrations/20200818203000_mutex.js @@ -0,0 +1,28 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const DISCORD_ID_MAX = 19; +const MUTEX = 'mutex'; + +/** + * Creates a table mapping two roles as mutually exclusive for a guild. + */ +exports.up = function(knex) { + return knex.schema.createTable(MUTEX, table => { + table.string('guild_id', DISCORD_ID_MAX); + table.string('role_id_1', DISCORD_ID_MAX); + table.string('role_id_2', DISCORD_ID_MAX); + + table.primary(['guild_id', 'role_id_1', 'role_id_2']); + }); +}; + +exports.down = function(knex) { + return knex.schema.dropTable(MUTEX); +}; + diff --git a/migrations/20211215035000_animated.js b/src/migrations/20211215035000_animated.js similarity index 53% rename from migrations/20211215035000_animated.js rename to src/migrations/20211215035000_animated.js index 51b6173..655b586 100644 --- a/migrations/20211215035000_animated.js +++ b/src/migrations/20211215035000_animated.js @@ -1,24 +1,24 @@ /******************************************************************************* - * This file is part of ReactionRoleBot, a role-assigning Discord bot. + * This file is part of No BS Role Reacts, a role-assigning Discord bot. * Copyright (C) 2020 Mimickal (Mia Moretti). * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. ******************************************************************************/ const REACTS = 'reacts'; const EMOJI_ID = 'emoji_id'; +/** + * Animated emojis did not exist when this bot was first written. When Discord + * added them, the bot did not recognize their format, so it dumped the entire + * "toString" value to the database. + * + * That logic has since been fixed. This migration correspondingly fixes any + * broken animated emoji entries already in the database. + */ exports.up = function(knex) { - console.error( + console.warn( 'Warning: this is a one-way migration. Animated emoji names cannot ' + 'be restored and will remain as IDs' ); diff --git a/src/migrations/20220102035000_multiple.js b/src/migrations/20220102035000_multiple.js new file mode 100644 index 0000000..1af122e --- /dev/null +++ b/src/migrations/20220102035000_multiple.js @@ -0,0 +1,110 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const { unindent } = require('../util'); + +const REACTS = 'reacts'; +const REACTS_TMP = `${REACTS}_tmp`; + +const OLD_ID_MAX = 19; +const NEW_ID_MAX = 22; + +/** + * - Update the reacts table to use role_id in the primary key, so that we can + * support one emoji mapped to multiple roles. + * - Increase Discord ID lengths from 19 to 22. SQLite3 doesn't enforce varchar + * length, so we don't NEED to do this, but we need to remake the table to + * modify the primary key anyway, so why not... + * + * SQLite3 does not support many alter table commands, so we need to create a + * new table, copy everything over, delete the old table, then rename the new + * table back to the original name. + * https://www.sqlitetutorial.net/sqlite-alter-table/ + * + * SQLite3 does not automatically reclaim the disk space consumed by the + * duplicate table. We need the VACUUM command for this, but VACUUM also cannot + * be used in a transaction. Knex runs all migrations in a transaction by + * default, so we will need to disable this functionality and use our own in + * order to VACUUM at the end. + */ +exports.config = { transaction: false }; + +exports.up = async function(knex) { + await knex.transaction(async trx => { + await trx.schema.createTable(REACTS_TMP, table => { + table.string('guild_id', NEW_ID_MAX); + table.string('message_id', NEW_ID_MAX); + table.string('emoji_id', NEW_ID_MAX); + table.string('role_id', NEW_ID_MAX); + + table.primary(['message_id', 'emoji_id', 'role_id']); + }); + await trx.insert(trx.select().from(REACTS)).into(REACTS_TMP); + await trx.schema.dropTable(REACTS); + await trx.schema.renameTable(REACTS_TMP, REACTS); + }); + await knex.raw('VACUUM'); +}; + +exports.down = async function(knex) { + console.warn(unindent(` + Rolling back ${REACTS} primary key change. This will delete instances + where multiple rows have the same message_id and emoji_id values, but + have different role_id values. + `)); + + await knex.transaction(async trx => { + // Print and delete rows with now-duplicate primary keys. + // SQLite3 doesn't support returning so we need this workaround. + const ambiguous_query = trx + .from(REACTS) + .leftJoin(`${REACTS} as self`, join => join + .on( `${REACTS}.message_id`, '=', 'self.message_id') + .andOn(`${REACTS}.emoji_id`, '=', 'self.emoji_id') + .andOn(`${REACTS}.role_id`, '!=', 'self.role_id') + ) + .whereNotNull('self.ROWID') + + const to_delete = await ambiguous_query + .clone() + .distinct(`${REACTS}.role_id`) + .select([ + `${REACTS}.guild_id`, + `${REACTS}.message_id`, + `${REACTS}.emoji_id`, + `${REACTS}.role_id`, + ]); + if (to_delete.length > 0) { + console.warn('\nDeleting the following:'); + console.warn(to_delete.map(JSON.stringify)); + } + + await trx + .delete() + .from(REACTS) + .whereIn(`${REACTS}.ROWID`, ambiguous_query + .clone() + .distinct(`${REACTS}.ROWID`) + ); + + // Now we can remake the table + await trx.schema.createTable(REACTS_TMP, table => { + table.string('guild_id', OLD_ID_MAX); + table.string('message_id', OLD_ID_MAX); + table.string('emoji_id', OLD_ID_MAX); + table.string('role_id', OLD_ID_MAX); + + table.primary(['message_id', 'emoji_id']); + }); + await trx.insert(trx.select().from(REACTS)).into(REACTS_TMP); + await trx.schema.dropTable(REACTS); + await trx.schema.renameTable(REACTS_TMP, REACTS); + }); + await knex.raw('VACUUM'); +}; + diff --git a/src/register.js b/src/register.js new file mode 100644 index 0000000..7e1926b --- /dev/null +++ b/src/register.js @@ -0,0 +1,48 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const fs = require('fs'); +const path = require('path'); + +const logger = require('./logger'); +const registry = require('./commands'); + +const WHAT_AM_I = ` +Registers slash commands with Discord's API. This only needs to be done once +after commands are updated. Updating commands globally can take some time to +propagate! For testing, use guild-specific commands (set guild_id in config). +`; + +if (!process.argv[2]) { + console.log(`Usage: ${ + process.argv.slice(0, 2).map(x => path.basename(x)).join(' ') + } \n${WHAT_AM_I}`); + process.exit(); +} + +const config = JSON.parse(fs.readFileSync(process.argv[2])); + +let output = 'Registering commmands '; +if (config.guild_id) { + output += `in guild ${config.guild_id}`; +} else { + output += 'GLOBALLY'; +} +output += ` for application ${config.app_id}...`; +logger.info(output); + +registry.registerCommands({ + application_id: config.app_id, + guild: config.guild_id, + token: config.token, +}) + .then(got_back => logger.info(`Successfully registered commands! Got data: ${ + JSON.stringify(got_back) + }`)) + .catch(err => logger.error(`Error registering commands:`, err)); + diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..4ac3850 --- /dev/null +++ b/src/util.js @@ -0,0 +1,201 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const { + Emoji, + Guild, + Interaction, + Message, + MessageReaction, + Role, + User, +} = require('discord.js'); + +const logger = require('./logger'); + +/** + * Most IDs are between 17 and 19 characters, but I have seen some patterns + * matching to 20 for custom emoji IDs, so let's just future-proof this and + * match up to 22. If this bot is still being used by the time we need to update + * that, well, cool. + */ +const DISCORD_ID_PATTERN = RegExp('^\\d{17,22}$'); + +/** + * Joins the given array of strings using newlines. + */ +function asLines(lines) { + if (!Array.isArray(lines)) { + lines = [lines]; + } + return lines.join('\n'); +} + +/** + * Like stringify, but provides more detail. Falls back on stringify. + */ +function detail(thing) { + if (thing instanceof Interaction) { + const int = thing; + return `${stringify(int.guild)} ${stringify(int.user)} ${stringify(int)}`; + } + else if (thing instanceof MessageReaction) { + const reaction = thing; + return `${stringify(reaction)} on ${stringify(reaction.message)}`; + } + else { + // Fall back on standard strings + return stringify(thing); + } +} + +/** + * Converts an emoji to a string we can use as a key (e.g. in a database). + * + * - Custom emojis (animated or otherwise) return their Discord ID. + * - Built-in emojis are already strings, and so we return them as-is. + * - Non-emoji strings are considered an error and will throw accordingly. + */ +function emojiToKey(emoji) { + if (!_isEmoji(emoji)) { + throw Error(`Not an emoji key: ${emoji}`); + } + return emoji?.id ?? emoji?.name ?? emoji; +} + +/** + * Flattens a MultiMap to an array of emoji-role pairs. + */ + function entries(mmap) { + return Array.from(mmap.keys()).reduce((arr, emoji_key) => { + arr.push(...mmap.get(emoji_key).map(role_id => [emoji_key, role_id])); + return arr; + }, new Array()); +} + +/** + * Same as {@link ephemReply}, but to edit an existing ephemeral response. + */ +function ephemEdit(interaction, content) { + logger.info(`Edit to ${detail(interaction)}: "${content}"`); + return interaction.editReply({ + content: content, + ephemeral: true, + }); +} + +/** + * Shortcut for sending an ephemeral reply to an interaction, since we do it so + * much. + */ +function ephemReply(interaction, content) { + logger.info(`Reply to ${detail(interaction)}: "${content}"`); + return interaction.reply({ + content: content, + ephemeral: true, + }); +} + +/** + * Handles both custom Discord.js Emojis and standard unicode emojis. + */ +function _isEmoji(thing) { + return isEmojiStr(thing) || thing instanceof Emoji; +} + +/** + * Matches Discord IDs. + */ +function isDiscordId(str) { + return str?.match?.(DISCORD_ID_PATTERN); +} + +/** + * Matches built-in unicode emoji literals. + */ +function isEmojiStr(str) { + return str?.match?.(/^\p{Extended_Pictographic}$/u); +} + +/** + * Given a Discord.js object, returns a logger-friendly string describing it in + * better detail. + * + * This purposely only outputs IDs to limit the amount of user data logged. + */ +// TODO stolen from Zerda. Maybe pull this out to a module with the logger? +function stringify(thing) { + if (!thing) { + return '[undefined]'; + } + else if (_isEmoji(thing)) { + const emoji = thing; + return `Emoji ${emojiToKey(emoji)}`; + } + else if (thing instanceof Guild) { + const guild = thing; + return `Guild ${guild.id}`; + } + else if (thing instanceof Interaction) { + const interaction = thing; + const cmd_str = Array.of( + interaction.commandName, + interaction.options.getSubcommandGroup(false), + interaction.options.getSubcommand(false), + ).filter(x => x).join(' '); + return `Interaction "${cmd_str}"`; + } + else if (thing instanceof Message) { + const message = thing; + return `Message ${message.url}`; + } + else if (thing instanceof MessageReaction) { + const reaction = thing; + return `Reaction ${emojiToKey(reaction.emoji)}`; + } + else if (thing instanceof Role) { + const role = thing; + return `Role ${role.id}`; + } + else if (thing instanceof User) { + const user = thing; + return `User ${user.id}`; + } + else if (Array.isArray(thing)) { + return thing.map(t => stringify(t)).join(', '); + } + else if (typeof thing === 'string' || thing instanceof String) { + return thing; + } + else { + return JSON.stringify(thing); + } +} + +/** + * Allows us to treat multi-line template strings as a single continuous line. + */ +function unindent(str) { + return str + .replace(/^\s*/, '') + .replace(/\n\t*/g, ' '); +} + +module.exports = { + asLines, + detail, + emojiToKey, + entries, + ephemEdit, + ephemReply, + isDiscordId, + isEmojiStr, + stringify, + unindent, +}; +