From 057bc992a166d94de4953882a4885a80716e3764 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Tue, 30 Jul 2024 10:45:10 -0500 Subject: [PATCH 01/52] Removed all projects since we're completly rewriting this. --- .dockerignore | 35 - .eslintignore | 1 - .eslintrc | 11 - .github/ISSUE_TEMPLATE/bug_report.md | 33 - .github/ISSUE_TEMPLATE/feature_request.md | 21 - .github/ISSUE_TEMPLATE/question.md | 13 - .github/PULL_REQUEST_TEMPLATE.md | 15 - .github/workflows/ci.yml | 44 - .github/workflows/docker-build.yml | 55 - .gitignore | 110 - Dockerfile | 22 - LICENSE | 21 - README.md | 145 - __mocks__/cron.js | 11 - __mocks__/discordMocks.js | 83 - __mocks__/providers.js | 26 - __mocks__/steamPage | 6545 --------- config/settings.json.example | 7 - jest.config.js | 4 - package-lock.json | 11227 ---------------- package.json | 47 - src/app.js | 66 - src/classes/Cache.js | 39 - src/classes/OffersCache.js | 54 - src/classes/OffersNotifier.js | 96 - src/classes/providers/AbstractProvider.js | 41 - src/classes/providers/EpicGamesProvider.js | 58 - src/classes/providers/ProviderFactory.js | 34 - src/classes/providers/SteamProvider.js | 57 - src/commands/config/DisableCommand.js | 32 - src/commands/config/SetChannelCommand.js | 52 - src/commands/misc/HelpCommand.js | 45 - src/commands/misc/OffersCommand.js | 73 - src/common/constants.js | 21 - src/common/context.js | 8 - test/classes/Cache.spec.js | 84 - test/classes/OffersCache.spec.js | 130 - test/classes/OffersNotifier.spec.js | 207 - .../providers/AbstractProvider.spec.js | 26 - .../providers/EpicGamesProvider.spec.js | 162 - .../classes/providers/ProviderFactory.spec.js | 44 - test/classes/providers/SteamProvider.spec.js | 122 - test/commands/config/DisableCommand.spec.js | 60 - .../commands/config/SetChannelCommand.spec.js | 90 - test/commands/misc/HelpCommand.spec.js | 65 - test/commands/misc/OffersCommand.spec.js | 143 - test/common/context.spec.js | 45 - test/setupEnv.js | 3 - 48 files changed, 20333 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .eslintignore delete mode 100644 .eslintrc delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/question.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/docker-build.yml delete mode 100644 .gitignore delete mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 __mocks__/cron.js delete mode 100644 __mocks__/discordMocks.js delete mode 100644 __mocks__/providers.js delete mode 100644 __mocks__/steamPage delete mode 100644 config/settings.json.example delete mode 100644 jest.config.js delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 src/app.js delete mode 100644 src/classes/Cache.js delete mode 100644 src/classes/OffersCache.js delete mode 100644 src/classes/OffersNotifier.js delete mode 100644 src/classes/providers/AbstractProvider.js delete mode 100644 src/classes/providers/EpicGamesProvider.js delete mode 100644 src/classes/providers/ProviderFactory.js delete mode 100644 src/classes/providers/SteamProvider.js delete mode 100644 src/commands/config/DisableCommand.js delete mode 100644 src/commands/config/SetChannelCommand.js delete mode 100644 src/commands/misc/HelpCommand.js delete mode 100644 src/commands/misc/OffersCommand.js delete mode 100644 src/common/constants.js delete mode 100644 src/common/context.js delete mode 100644 test/classes/Cache.spec.js delete mode 100644 test/classes/OffersCache.spec.js delete mode 100644 test/classes/OffersNotifier.spec.js delete mode 100644 test/classes/providers/AbstractProvider.spec.js delete mode 100644 test/classes/providers/EpicGamesProvider.spec.js delete mode 100644 test/classes/providers/ProviderFactory.spec.js delete mode 100644 test/classes/providers/SteamProvider.spec.js delete mode 100644 test/commands/config/DisableCommand.spec.js delete mode 100644 test/commands/config/SetChannelCommand.spec.js delete mode 100644 test/commands/misc/HelpCommand.spec.js delete mode 100644 test/commands/misc/OffersCommand.spec.js delete mode 100644 test/common/context.spec.js delete mode 100644 test/setupEnv.js diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index d916a3b..0000000 --- a/.dockerignore +++ /dev/null @@ -1,35 +0,0 @@ -# OS Generated -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Thrashes -ehthumbs.db -Thumbs.db - -# IDE -.vscode/ -.idea/ - -# Node -node_modules -npm-debug.log - -# Development -config/settings.json -data -dev-data - -# Tests -__mocks__ -test - -# Git -.github -.git - -# Root files -.eslintignore -.eslintrc -.gitignore -jest.config.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c2658d7..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 35a7b8e..0000000 --- a/.eslintrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": [ - "greencoast/node" - ], - "rules": { - "one-var": "off" - }, - "parserOptions": { - "ecmaVersion": 2020 - } -} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 3d5df47..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: "Bug Report" -about: Use this template to report a bug. -labels: "Type: Bug" ---- - -#### :bug: Describe the Bug - -> A clear and concise description of what the bug is. - -#### :pencil2: Steps to Reproduce - -> 1. Go to '...' -> 2. Type '...' -> 3. See error... - -#### :confused: Expected Behavior - -> A description of what you expected to get (if applicable). - -#### :scroll: Log - -``` text -Paste a relevant portion of the console log here. -``` - -#### :camera: Screenshots - -> Paste a screenshot of the bug (if applicable). - -#### :question: Other Information - -* Node Version: `Version of the node runtime.` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 409e0b2..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: "Feature Request" -about: Use this template to request a new feature or if you have a suggestion. -labels: "Type: Feature Request" ---- - -#### :zap: Describe the New Feature - -> A clear and concise description of what the new feature is. - -#### :pencil2: Functionality - -> Describe the functionality of the new feature as a steps list (for easier understanding). - -> 1. Go to '...' -> 2. Type '...' -> 3. See error... - -#### :question: Additional Information - -> If applicable, you can add anything else here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index a1651b0..0000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: "Question" -about: Use this template if you have any questions. Preferably use the Discord (check the README). -labels: "Type: Question" ---- - -#### :confused: Question - -> A description of what you need help with. - -#### :question: Additional Information - -> Anything additional goes here (screenshots, logs, etc...) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 9999bb2..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,15 +0,0 @@ -### :pencil: Checklist - -Make sure that your PR fulfills these requirements: - -- [ ] Tests have been added for this feature. -- [ ] All tests pass on your local machine. -- [ ] Code has been linted with the proper rules. - -### :page_facing_up: Description - -> Add a brief description of your PR. - -### :pushpin: Does this PR address any issue? - -> If so, add the # of the issue this is addressing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 6d92a65..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: CI - -on: - push: - branches: [master] - pull_request: - branches: [master] - workflow_dispatch: - -jobs: - test: - name: Run Tests - runs-on: ubuntu-latest - - steps: - - name: Checkout the Repository - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Install Dependencies - run: npm ci - - - name: Lint Files - run: npm run lint - - - name: Run Tests - run: npm run test:once - - build_push: - name: Trigger Docker Build - runs-on: ubuntu-latest - needs: test - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.test.result == 'success' }} - - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v1 - with: - token: ${{ secrets.REPO_ACCESS_TOKEN }} - event-type: docker-build diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index 09073cb..0000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Docker Build and Push - -on: - repository_dispatch: - types: [docker-build] - workflow_dispatch: - -jobs: - build_push: - name: Build Docker Image and Push to Docker Hub - runs-on: ubuntu-latest - - steps: - - name: Checkout the Repository - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Cache Docker Layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: moonstarx - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get Version From package.json - id: package-version - uses: martinbeentjes/npm-get-version-action@master - - - name: Get Current Date - id: date - run: echo "::set-output name=date::$(date +'%Y-%m-%d')" - - - name: Push to DockerHub - id: docker_build - uses: docker/build-push-action@v2 - with: - push: true - tags: moonstarx/discord-free-games-notifier:latest - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - build-args: | - DATE_CREATED=${{ steps.date.outputs.date }} - VERSION=${{ steps.package-version.outputs.current-version }} - - - name: Image Digest - run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e13c3bf..0000000 --- a/.gitignore +++ /dev/null @@ -1,110 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Config -config/settings.json - -# Data -data/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index cbb8036..0000000 --- a/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM node:12.20.0-alpine3.12 - -ARG DATE_CREATED -ARG VERSION - -LABEL org.opencontainers.image.created=$DATE_CREATED -LABEL org.opencontainers.image.version=$VERSION -LABEL org.opencontainers.image.authors="moonstar-x" -LABEL org.opencontainers.image.vendor="moonstar-x" -LABEL org.opencontainers.image.title="Discord Free Games Notifier" -LABEL org.opencontainers.image.description="A Discord bot that will notify when free games on Steam or Epic Games come out." -LABEL org.opencontainers.image.source="https://github.com/moonstar-x/discord-free-games-notifier" - -WORKDIR /opt/app - -COPY package*.json ./ - -RUN npm ci --only=prod - -COPY . . - -CMD ["npm", "start"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c17e42c..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 moonstar-x - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 784ae75..0000000 --- a/README.md +++ /dev/null @@ -1,145 +0,0 @@ -[![discord](https://img.shields.io/discord/730998659008823296.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/mhj3Zsv) -[![ci-build-status](https://img.shields.io/github/workflow/status/moonstar-x/discord-free-games-notifier/CI?logo=github)](https://github.com/moonstar-x/discord-free-games-notifier) -[![open-issues-count](https://img.shields.io/github/issues-raw/moonstar-x/discord-free-games-notifier?logo=github)](https://github.com/moonstar-x/discord-free-games-notifier) -[![docker-image-size](https://img.shields.io/docker/image-size/moonstarx/discord-free-games-notifier?logo=docker)](https://hub.docker.com/repository/docker/moonstarx/discord-free-games-notifier) -[![docker-pulls](https://img.shields.io/docker/pulls/moonstarx/discord-free-games-notifier?logo=docker)](https://hub.docker.com/repository/docker/moonstarx/discord-free-games-notifier) - -# Discord Free Games Notifier - -A Discord bot that will notify when free games on Steam or Epic Games come out. The bot will fetch offers from Steam and Epic Games every 30 minutes and will send a notification message to the set channel once a new offer is found. - -## Requirements - -To self-host this bot, you'll need the following: - -* [git](https://git-scm.com/) -* [node.js](https://nodejs.org/en/) (Version 12 or higher is required.) - -## Installation - -In order to self-host this bot, first you'll need to clone this repository. - -```text -git clone https://github.com/moonstar-x/discord-free-games-notifier.git -``` - -Once cloned, proceed to install the dependencies: - -```text -npm ci --only=prod -``` - -Or, if you prefer to install everything including `devDependencies`, you may run: - -```text -npm install -``` - -After you have [configured](#configuration) your bot, you can run it with: - -```text -npm start -``` - -## Configuration - -There are two ways to configure the bot, one with a `settings.json` file inside the `config` folder or with environment variables. - -Here's a table with the available options you may configure: - -| Environment Variable | JSON Property | Required | Type | Description | -|-----------------------------------|-----------------------------|-----------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------| -| DISCORD_TOKEN | `token` | Yes. | `string` | The bot's token. | -| DISCORD_PREFIX | `prefix` | No. (Defaults to: `$`) | `string` | The bot's prefix. Used for the commands. | -| DISCORD_OWNER_ID | `owner_id` | No. (Defaults to: `null`) | `string` or `null` | The ID of the bot's owner. | -| DISCORD_OWNER_REPORTING | `owner_reporting` | No. (Defaults to: `false`) | `boolean` | Whether the bot should send error reports to the owner via DM when a command errors. | -| DISCORD_PRESENCE_REFRESH_INTERVAL | `presence_refresh_interval` | No. (Defaults to: `900000`) | `number` or `null` | The time interval in ms in which the bot updates its presence. If set to `null` the presence auto update will be disabled. | - -> **Note on `Required`**: A required settings HAS to be in the JSON or environment variables. -> -> **Note on `Default`**: If a setting is missing from the JSON or environment variables, the default value takes place. -> -> * To see how to find the IDs for users or channels, you can check out [this guide](<https://github.com/moonstar-x/discord-downtime-notifier/wiki/Getting-User,-Channel-and-Server-IDs>). -> * If you don't have a Discord token yet, you can see a guide on how to get one [here](<https://github.com/moonstar-x/discord-downtime-notifier/wiki/Getting-a-Discord-Bot-Token>). - -### Using the Config File - -Inside the `config` folder you will see a file named `settings.json.example`, rename it to `settings.json` and replace all the properties with your own values. - -Your file should look like this: - -```json -{ - "token": "YOUR_DISCORD_TOKEN", - "prefix": "!", - "owner_id": "YOUR_USER_ID", - "owner_reporting": false, - "presence_refresh_interval": 900000 -} -``` - -## Usage - -You can start the bot by running: - -```text -npm start -``` - -You'll need to configure which channel should be used for the bot to send notifications. To do this, run the command: - -```text -n!setchannel <channel_mention> -``` - -> Replace `n!` with your actual bot prefix and `<channel_mention>` with the mention of the channel you wish to set. -> -> Make sure that the channel you're setting is viewable by the bot and that you have the `MANAGE_CHANNELS` permission. - -## Commands - -The following commands are available: - -| Command | Aliases | Description | -|----------------------------------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `n!help` | `n!h` | Receive a message with the available commands. | -| `n!setchannel <channel_mention>` | `n!channel` | Set the channel that should be used for the bot to send the automatic game offer announcements. The user issuing this command must have the `MANAGE_CHANNELS` permission. | -| `n!disable` | | Disable the automatic game offer announcements on this server. You can still use the `n!offers` command. The user issuing this command must have the `MANAGE_CHANNELS` permission. | -| `n!offers <provider>` | | Get a list of current available offers. Replace `<provider>` with **epic** for Epic Games Store offers or **steam** for Steam offers. You can omit this argument to receive all offers from all providers supported. | - -## Docker Support - -You can use the bot through Docker. - -### Volumes - -You may use the following volumes: - -| Volume | Description | -|-----------------|------------------------------------------------------------------------------------------------------------------------------| -| /opt/app/config | Volume where the config file is located. Generally not necessary since you can configure the bot with environment variables. | -| /opt/app/data | Volume where the data folder is located. Here you can find the sqlite database file. Required to set up. | - -### Environment Variables - -You can configure the bot using environment variables. To do so, check out [configuration](#configuration) for a full list of what environment variables are used. - -### Starting the Container - -Starting the bot's container can be done by running: - -```text -docker run -it -e DISCORD_TOKEN="YOUR DISCORD TOKEN" -v "/local/folder/for/data":"/opt/app/data" moonstarx/discord-free-games-notifier:latest -``` - -## Add this bot to your server - -You can add this bot to your server by clicking in the image below: - -[![Add this bot to your server](https://i.imgur.com/SVAwPTU.png)](https://discord.com/oauth2/authorize?client_id=795561965954269205&scope=bot&permissions=2048) - -> The prefix for this bot is `n!` - -## Author - -This bot was made by [moonstar-x](https://github.com/moonstar-x). diff --git a/__mocks__/cron.js b/__mocks__/cron.js deleted file mode 100644 index d4d6805..0000000 --- a/__mocks__/cron.js +++ /dev/null @@ -1,11 +0,0 @@ -const CronJob = jest.fn(function(_, action, onComplete) { - this.onComplete = onComplete; - this.nextDate = jest.fn(() => ({ format: jest.fn(() => 'time') })); - this.start = jest.fn(() => action.bind(this)()); - - return this; -}); - -module.exports = { - CronJob -}; diff --git a/__mocks__/discordMocks.js b/__mocks__/discordMocks.js deleted file mode 100644 index 7c277bc..0000000 --- a/__mocks__/discordMocks.js +++ /dev/null @@ -1,83 +0,0 @@ -const { Collection } = require('discord.js'); - -const channelMock = { - name: 'channel', - id: '123', - viewable: true, - send: jest.fn(() => Promise.resolve()), - guild: { - name: 'guild' - } -}; - -const guildMock = { - name: 'guild', - id: '123', - channels: { - cache: new Collection([[channelMock.id, channelMock], [channelMock.id, channelMock]]) - } -}; - -const commandMock = { - name: 'command', - description: 'description' -}; - -const commandGroupMock = { - name: 'group', - commands: [commandMock, commandMock] -}; - -const clientMock = { - handleCommandError: jest.fn(), - registry: { - groups: [commandGroupMock, commandGroupMock] - }, - setProvider: jest.fn(() => Promise.resolve()), - updatePresence: jest.fn(), - dataProvider: { - set: jest.fn(), - get: jest.fn(), - clear: jest.fn(), - getGlobal: jest.fn(), - setGlobal: jest.fn(), - clearGlobal: jest.fn() - }, - guilds: { - cache: new Collection([[guildMock.id, guildMock], [guildMock.id, guildMock]]) - } -}; - -const userMock = { - username: 'user' -}; - -const memberMock = { - displayName: 'nickname' -}; - -const messageMock = { - reply: jest.fn(), - guild: guildMock, - author: userMock, - member: memberMock, - channel: { - send: jest.fn() - }, - mentions: { - channels: { - first: jest.fn(() => channelMock) - } - } -}; - -module.exports = { - guildMock, - clientMock, - messageMock, - userMock, - memberMock, - commandGroupMock, - commandMock, - channelMock -}; diff --git a/__mocks__/providers.js b/__mocks__/providers.js deleted file mode 100644 index 8c7a14a..0000000 --- a/__mocks__/providers.js +++ /dev/null @@ -1,26 +0,0 @@ -const offerMock = { - game: 'name', - provider: 'provider', - url: 'url', - id: 'id' -}; - -const providerMock = { - getOffers: jest.fn(() => Promise.resolve([offerMock])) -}; - -const factoryMock = { - getAll: jest.fn(() => [providerMock, providerMock]), - getInstance: jest.fn((name) => { - if (name === 'valid') { - return providerMock; - } - throw new TypeError('Invalid provider'); - }) -}; - -module.exports = { - offerMock, - providerMock, - factoryMock -}; diff --git a/__mocks__/steamPage b/__mocks__/steamPage deleted file mode 100644 index 49aa356..0000000 --- a/__mocks__/steamPage +++ /dev/null @@ -1,6545 +0,0 @@ -<!DOCTYPE html> -<html class=" responsive" lang="en"> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <meta name="theme-color" content="#171a21"> - <title>Steam Search</title> - <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> - - - - <link href="https://store.cloudflare.steamstatic.com/public/shared/css/motiva_sans.css?v=2C1Oh9QFVTyK&l=english&_cdn=cloudflare" rel="stylesheet" type="text/css" > -<link href="https://store.cloudflare.steamstatic.com/public/shared/css/shared_global.css?v=8Fgq6R88cOs3&l=english&_cdn=cloudflare" rel="stylesheet" type="text/css" > -<link href="https://store.cloudflare.steamstatic.com/public/shared/css/buttons.css?v=hFJKQ6HV7IKT&l=english&_cdn=cloudflare" rel="stylesheet" type="text/css" > -<link href="https://store.cloudflare.steamstatic.com/public/css/v6/store.css?v=tp0vUvl4iOEk&l=english&_cdn=cloudflare" rel="stylesheet" type="text/css" > -<link href="https://store.cloudflare.steamstatic.com/public/css/v6/browse.css?v=7hoqLVcZ7KVq&l=english&_cdn=cloudflare" rel="stylesheet" type="text/css" > -<link href="https://store.cloudflare.steamstatic.com/public/css/v6/search.css?v=7wOcDLyifOOX&l=english&_cdn=cloudflare" rel="stylesheet" type="text/css" > -<link href="https://store.cloudflare.steamstatic.com/public/shared/css/shared_responsive.css?v=3P0pMcp4t3C-&l=english&_cdn=cloudflare" rel="stylesheet" type="text/css" > - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-33786258-1', 'auto', { - 'sampleRate': 0.4 }); - ga('set', 'dimension1', false ); - ga('set', 'dimension2', 'External' ); - ga('set', 'dimension3', 'search' ); - ga('set', 'dimension4', "search\/search" ); - ga('send', 'pageview' ); - - </script> - <script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/shared/javascript/jquery-1.8.3.min.js?v=.TZ2NKhB-nliU&_cdn=cloudflare" ></script> -<script type="text/javascript">$J = jQuery.noConflict();</script><script type="text/javascript">VALVE_PUBLIC_PATH = "https:\/\/store.cloudflare.steamstatic.com\/public\/";</script><script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/shared/javascript/tooltip.js?v=.9Z1XDV02xrml&_cdn=cloudflare" ></script> - -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/shared/javascript/shared_global.js?v=RVua47VPZG4D&l=english&_cdn=cloudflare" ></script> - -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/javascript/main.js?v=8h6T9Tm2gQuU&l=english&_cdn=cloudflare" ></script> - -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/javascript/dynamicstore.js?v=VOY_IZ8UqtCC&l=english&_cdn=cloudflare" ></script> - -<script type="text/javascript"> - var __PrototypePreserve=[]; - __PrototypePreserve[0] = Array.from; - __PrototypePreserve[1] = Function.prototype.bind; -</script> -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/javascript/prototype-1.7.js?v=.a38iP7Khdmyy&_cdn=cloudflare" ></script> -<script type="text/javascript"> - Array.from = __PrototypePreserve[0] || Array.from; - Function.prototype.bind = __PrototypePreserve[1] || Function.prototype.bind; -</script> -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/javascript/scriptaculous/_combined.js?v=Me1IBxzktiwk&l=english&_cdn=cloudflare&load=effects,controls,slider" ></script> - -<script type="text/javascript">Object.seal && [ Object, Array, String, Number ].map( function( builtin ) { Object.seal( builtin.prototype ); } );</script> - <script type="text/javascript"> - document.addEventListener('DOMContentLoaded', function(event) { - $J.data( document, 'x_readytime', new Date().getTime() ); - $J.data( document, 'x_oldref', GetNavCookie() ); - SetupTooltips( { tooltipCSSClass: 'store_tooltip'} ); - }); - </script><script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/javascript/dselect.js?v=WptLSjNFb9fP&l=english&_cdn=cloudflare" ></script> -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/javascript/searchpage.js?v=sYXUgpNP3ajh&l=english&_cdn=cloudflare" ></script> -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/javascript/jquery.filter.js?v=.1afC7xudQcY4&_cdn=cloudflare" ></script> -<script type="text/javascript" src="https://store.cloudflare.steamstatic.com/public/shared/javascript/shared_responsive_adapter.js?v=gcLGc1YQkPQi&l=english&_cdn=cloudflare" ></script> - - <meta name="twitter:card" content="summary_large_image"> - - <meta name="twitter:site" content="@steam" /> - - <meta property="og:title" content="Steam Search"> - <meta property="twitter:title" content="Steam Search"> - <meta property="og:type" content="website"> - <meta property="fb:app_id" content="105386699540688"> - <meta property="og:site" content="Steam"> - - - <link rel="image_src" href="https://store.cloudflare.steamstatic.com/public/shared/images/responsive/share_steam_logo.png"> - <meta property="og:image" content="https://store.cloudflare.steamstatic.com/public/shared/images/responsive/share_steam_logo.png"> - <meta name="twitter:image" content="https://store.cloudflare.steamstatic.com/public/shared/images/responsive/share_steam_logo.png" /> - <meta property="og:image:secure" content="https://store.cloudflare.steamstatic.com/public/shared/images/responsive/share_steam_logo.png"> - - - - - </head> -<body class="v6 search_page responsive_page"> - -<div class="responsive_page_frame with_header"> - <div class="responsive_page_menu_ctn mainmenu"> - <div class="responsive_page_menu" id="responsive_page_menu"> - <div class="mainmenu_contents"> - <div class="mainmenu_contents_items"> - <a class="menuitem" href="https://store.steampowered.com/login/?redir=search%2F%3Fmaxprice%3Dfree%26specials%3D1&redir_ssl=1&snr=1_7_7_2300_global-header"> - Login </a> - <a class="menuitem supernav" href="https://store.steampowered.com/?snr=1_7_7_2300_global-responsive-menu" data-tooltip-type="selector" data-tooltip-content=".submenu_store"> - Store </a> - <div class="submenu_store" style="display: none;" data-submenuid="store"> - <a class="submenuitem" href="https://store.steampowered.com/?snr=1_7_7_2300_global-responsive-menu">Home</a> - <a class="submenuitem" href="https://store.steampowered.com/explore/?snr=1_7_7_2300_global-responsive-menu">Discovery Queue</a> - <a class="submenuitem" href="https://steamcommunity.com/my/wishlist/">Wishlist</a> - <a class="submenuitem" href="https://store.steampowered.com/points/shop/?snr=1_7_7_2300_global-responsive-menu">Points Shop</a> - <a class="submenuitem" href="https://store.steampowered.com/news/?snr=1_7_7_2300_global-responsive-menu">News</a> - <a class="submenuitem" href="https://store.steampowered.com/stats/?snr=1_7_7_2300_global-responsive-menu">Stats</a> - </div> - - - <a class="menuitem supernav" style="display: block" href="https://steamcommunity.com/" data-tooltip-type="selector" data-tooltip-content=".submenu_community"> - Community </a> - <div class="submenu_community" style="display: none;" data-submenuid="community"> - <a class="submenuitem" href="https://steamcommunity.com/">Home</a> - <a class="submenuitem" href="https://steamcommunity.com/discussions/">Discussions</a> - <a class="submenuitem" href="https://steamcommunity.com/workshop/">Workshop</a> - <a class="submenuitem" href="https://steamcommunity.com/market/">Market</a> - <a class="submenuitem" href="https://steamcommunity.com/?subsection=broadcasts">Broadcasts</a> - </div> - - - - - <a class="menuitem" href="https://help.steampowered.com/en/"> - Support </a> - - <div class="minor_menu_items"> - <div class="menuitem change_language_action"> - Change language </div> - <div class="menuitem" onclick="Responsive_RequestDesktopView();"> - View desktop website </div> - </div> - </div> - <div class="mainmenu_footer_spacer "></div> - <div class="mainmenu_footer"> - <div class="mainmenu_footer_logo"><img src="https://store.cloudflare.steamstatic.com/public/shared/images/responsive/logo_valve_footer.png"></div> - © Valve Corporation. All rights reserved. All trademarks are property of their respective owners in the US and other countries. <span class="mainmenu_valve_links"> - <a href="https://store.steampowered.com/privacy_agreement/?snr=1_7_7_2300_global-responsive-menu" target="_blank">Privacy Policy</a> - | <a href="http://www.valvesoftware.com/legal.htm" target="_blank">Legal</a> - | <a href="https://store.steampowered.com/subscriber_agreement/?snr=1_7_7_2300_global-responsive-menu" target="_blank">Steam Subscriber Agreement</a> - | <a href="https://store.steampowered.com/steam_refunds/?snr=1_7_7_2300_global-responsive-menu" target="_blank">Refunds</a> - </span> - </div> - </div> - </div> - </div> - - <div class="responsive_local_menu_tab"> - - </div> - - <div class="responsive_page_menu_ctn localmenu"> - <div class="responsive_page_menu" id="responsive_page_local_menu"> - <div class="localmenu_content"> - </div> - </div> - </div> - - - - <div class="responsive_header"> - <div class="responsive_header_content"> - <div id="responsive_menu_logo"> - <img src="https://store.cloudflare.steamstatic.com/public/shared/images/responsive/header_menu_hamburger.png" height="100%"> - </div> - <div class="responsive_header_logo"> - <a href="https://store.steampowered.com/?snr=1_7_7_2300_global-responsive-menu"> - <img src="https://store.cloudflare.steamstatic.com/public/shared/images/responsive/header_logo.png" height="36" border="0" alt="STEAM"> - </a> - </div> - </div> - </div> - - <div class="responsive_page_content_overlay"> - - </div> - - <div class="responsive_fixonscroll_ctn nonresponsive_hidden "> - </div> - - <div class="responsive_page_content"> - - <div id="global_header" data-panel="{"flow-children":"row"}"> - <div class="content"> - <div class="logo"> - <span id="logo_holder"> - <a href="https://store.steampowered.com/?snr=1_7_7_2300_global-header"> - <img src="https://store.cloudflare.steamstatic.com/public/shared/images/header/logo_steam.svg?t=962016" width="176" height="44"> - </a> - </span> - </div> - - <div class="supernav_container"> - <a class="menuitem supernav" href="https://store.steampowered.com/?snr=1_7_7_2300_global-header" data-tooltip-type="selector" data-tooltip-content=".submenu_store"> - STORE </a> - <div class="submenu_store" style="display: none;" data-submenuid="store"> - <a class="submenuitem" href="https://store.steampowered.com/?snr=1_7_7_2300_global-header">Home</a> - <a class="submenuitem" href="https://store.steampowered.com/explore/?snr=1_7_7_2300_global-header">Discovery Queue</a> - <a class="submenuitem" href="https://steamcommunity.com/my/wishlist/">Wishlist</a> - <a class="submenuitem" href="https://store.steampowered.com/points/shop/?snr=1_7_7_2300_global-header">Points Shop</a> - <a class="submenuitem" href="https://store.steampowered.com/news/?snr=1_7_7_2300_global-header">News</a> - <a class="submenuitem" href="https://store.steampowered.com/stats/?snr=1_7_7_2300_global-header">Stats</a> - </div> - - - <a class="menuitem supernav" style="display: block" href="https://steamcommunity.com/" data-tooltip-type="selector" data-tooltip-content=".submenu_community"> - COMMUNITY </a> - <div class="submenu_community" style="display: none;" data-submenuid="community"> - <a class="submenuitem" href="https://steamcommunity.com/">Home</a> - <a class="submenuitem" href="https://steamcommunity.com/discussions/">Discussions</a> - <a class="submenuitem" href="https://steamcommunity.com/workshop/">Workshop</a> - <a class="submenuitem" href="https://steamcommunity.com/market/">Market</a> - <a class="submenuitem" href="https://steamcommunity.com/?subsection=broadcasts">Broadcasts</a> - </div> - - - - <a class="menuitem" href="https://store.steampowered.com/about/?snr=1_7_7_2300_global-header"> - ABOUT </a> - - <a class="menuitem" href="https://help.steampowered.com/en/"> - SUPPORT </a> - </div> - <script type="text/javascript"> - jQuery(function($) { - $('#global_header .supernav').v_tooltip({'location':'bottom', 'destroyWhenDone': false, 'tooltipClass': 'supernav_content', 'offsetY':-4, 'offsetX': 1, 'horizontalSnap': 4, 'tooltipParent': '#global_header .supernav_container', 'correctForScreenSize': false}); - }); - </script> - - <div id="global_actions"> - <div id="global_action_menu"> - <div class="header_installsteam_btn header_installsteam_btn_green"> - - <a class="header_installsteam_btn_content" href="https://store.steampowered.com/about/?snr=1_7_7_2300_global-header"> - Install Steam </a> - </div> - - - <a class="global_action_link" href="https://store.steampowered.com/login/?redir=search%2F%3Fmaxprice%3Dfree%26specials%3D1&redir_ssl=1&snr=1_7_7_2300_global-header">login</a> - | - <span class="pulldown global_action_link" id="language_pulldown" onclick="ShowMenu( this, 'language_dropdown', 'right' );">language</span> - <div class="popup_block_new" id="language_dropdown" style="display: none;"> - <div class="popup_body popup_menu"> - <a class="popup_menu_item tight" href="?l=schinese&maxprice=free&specials=1" onclick="ChangeLanguage( 'schinese' ); return false;">简体中文 (Simplified Chinese)</a> - <a class="popup_menu_item tight" href="?l=tchinese&maxprice=free&specials=1" onclick="ChangeLanguage( 'tchinese' ); return false;">繁體中文 (Traditional Chinese)</a> - <a class="popup_menu_item tight" href="?l=japanese&maxprice=free&specials=1" onclick="ChangeLanguage( 'japanese' ); return false;">日本語 (Japanese)</a> - <a class="popup_menu_item tight" href="?l=koreana&maxprice=free&specials=1" onclick="ChangeLanguage( 'koreana' ); return false;">한국어 (Korean)</a> - <a class="popup_menu_item tight" href="?l=thai&maxprice=free&specials=1" onclick="ChangeLanguage( 'thai' ); return false;">ไทย (Thai)</a> - <a class="popup_menu_item tight" href="?l=bulgarian&maxprice=free&specials=1" onclick="ChangeLanguage( 'bulgarian' ); return false;">Български (Bulgarian)</a> - <a class="popup_menu_item tight" href="?l=czech&maxprice=free&specials=1" onclick="ChangeLanguage( 'czech' ); return false;">Čeština (Czech)</a> - <a class="popup_menu_item tight" href="?l=danish&maxprice=free&specials=1" onclick="ChangeLanguage( 'danish' ); return false;">Dansk (Danish)</a> - <a class="popup_menu_item tight" href="?l=german&maxprice=free&specials=1" onclick="ChangeLanguage( 'german' ); return false;">Deutsch (German)</a> - <a class="popup_menu_item tight" href="?l=spanish&maxprice=free&specials=1" onclick="ChangeLanguage( 'spanish' ); return false;">Español - España (Spanish - Spain)</a> - <a class="popup_menu_item tight" href="?l=latam&maxprice=free&specials=1" onclick="ChangeLanguage( 'latam' ); return false;">Español - Latinoamérica (Spanish - Latin America)</a> - <a class="popup_menu_item tight" href="?l=greek&maxprice=free&specials=1" onclick="ChangeLanguage( 'greek' ); return false;">Ελληνικά (Greek)</a> - <a class="popup_menu_item tight" href="?l=french&maxprice=free&specials=1" onclick="ChangeLanguage( 'french' ); return false;">Français (French)</a> - <a class="popup_menu_item tight" href="?l=italian&maxprice=free&specials=1" onclick="ChangeLanguage( 'italian' ); return false;">Italiano (Italian)</a> - <a class="popup_menu_item tight" href="?l=hungarian&maxprice=free&specials=1" onclick="ChangeLanguage( 'hungarian' ); return false;">Magyar (Hungarian)</a> - <a class="popup_menu_item tight" href="?l=dutch&maxprice=free&specials=1" onclick="ChangeLanguage( 'dutch' ); return false;">Nederlands (Dutch)</a> - <a class="popup_menu_item tight" href="?l=norwegian&maxprice=free&specials=1" onclick="ChangeLanguage( 'norwegian' ); return false;">Norsk (Norwegian)</a> - <a class="popup_menu_item tight" href="?l=polish&maxprice=free&specials=1" onclick="ChangeLanguage( 'polish' ); return false;">Polski (Polish)</a> - <a class="popup_menu_item tight" href="?l=portuguese&maxprice=free&specials=1" onclick="ChangeLanguage( 'portuguese' ); return false;">Português (Portuguese)</a> - <a class="popup_menu_item tight" href="?l=brazilian&maxprice=free&specials=1" onclick="ChangeLanguage( 'brazilian' ); return false;">Português - Brasil (Portuguese - Brazil)</a> - <a class="popup_menu_item tight" href="?l=romanian&maxprice=free&specials=1" onclick="ChangeLanguage( 'romanian' ); return false;">Română (Romanian)</a> - <a class="popup_menu_item tight" href="?l=russian&maxprice=free&specials=1" onclick="ChangeLanguage( 'russian' ); return false;">Русский (Russian)</a> - <a class="popup_menu_item tight" href="?l=finnish&maxprice=free&specials=1" onclick="ChangeLanguage( 'finnish' ); return false;">Suomi (Finnish)</a> - <a class="popup_menu_item tight" href="?l=swedish&maxprice=free&specials=1" onclick="ChangeLanguage( 'swedish' ); return false;">Svenska (Swedish)</a> - <a class="popup_menu_item tight" href="?l=turkish&maxprice=free&specials=1" onclick="ChangeLanguage( 'turkish' ); return false;">Türkçe (Turkish)</a> - <a class="popup_menu_item tight" href="?l=vietnamese&maxprice=free&specials=1" onclick="ChangeLanguage( 'vietnamese' ); return false;">Tiếng Việt (Vietnamese)</a> - <a class="popup_menu_item tight" href="?l=ukrainian&maxprice=free&specials=1" onclick="ChangeLanguage( 'ukrainian' ); return false;">Українська (Ukrainian)</a> - <a class="popup_menu_item tight" href="http://translation.steampowered.com" target="_blank">Help us translate Steam</a> - </div> - </div> - </div> - </div> - </div> -</div> -<div id="responsive_store_nav_ctn"></div><div id="responsive_store_nav_overlay" style="display:none;"><div id="responsive_store_nav_overlay_ctn"></div><div id="responsive_store_nav_overlay_bottom"></div></div><div data-cart-banner-spot="1"></div> - <div class="responsive_page_template_content" data-panel="{"autoFocus":true}" > - - -<script type="text/javascript"> - - jQuery( document ).ready(function( $ ) { - - // when we create the responsive right column menu, it moves several hidden inputs out of the form which breaks searching, so - // we reparent any hidden inputs in the right column to a spot at the bottom of the form with other hidden elements. - // this selector only works once, so after moving from responsive to desktop mode the elements will stay in the hidden searchform area, but that's ok. - // they work just as well from there. - Responsive_ReparentItemsInResponsiveMode( '#additional_search_options input[type=hidden]', $J('#hidden_searchform_elements') ); - - - var bInfiniScroll = true; - var nItemCount = 1; - - if ( nItemCount > 0 && bInfiniScroll ) - { - InitInfiniteScroll.bEnabled = true; - InitInfiniteScroll.nScrollSize = 50; - } - - InitSearchPage(); - - UpdateTags(); - - InitAutocollapse(); - - // Handle our user hitting 'back' cleanly. This needs to trigger after the Dynamic - // Store has finished, or (if no dynamic store) just after the page renders. - // - // Dynamic Store will trigger its OnReady immediately if it's already complete. - if ( GDynamicStore ) - GDynamicStore.OnReady( function() { setTimeout( HandleBackReposition, 500 ) } ); - else - window.addEventListener( 'load', function() { setTimeout( HandleBackReposition, 500 ) } ); - }); - -</script> - - -<div class="page_header_ctn search "> - - <div id="store_header" class=""> - <div class="content"> - <div id="store_controls"> - <div id="cart_status_data"> - <div class="store_header_btn_green store_header_btn" id="store_header_cart_btn" style="display: none;"> - <div class="store_header_btn_caps store_header_btn_leftcap"></div> - <div class="store_header_btn_caps store_header_btn_rightcap"></div> - <a id="cart_link" class="store_header_btn_content" href="https://store.steampowered.com/cart/?snr=1_7_7_2300_12"> - Cart (<span id="cart_item_count_value">0</span>) - </a> - </div> - </div> - </div> - - <div id="store_nav_area"> - <div class="store_nav_leftcap"></div> - <div class="store_nav_bg"> - <div class="store_nav" data-panel="{"flow-children":"row"}" > - - - <div class="tab flyout_tab " id="foryou_tab" - data-flyout="foryou_flyout" data-flyout-align="left" data-flyout-valign="bottom" data-flyout-delay="300" - data-panel="{"focusable":true}" > - <span class="pulldown"> - <a class="pulldown_desktop" href="https://store.steampowered.com/?snr=1_7_7_2300_12">Your Store</a> - <a class="pulldown_mobile" href="#">Your Store</a> - <span></span> - </span> - </div> - <div class="popup_block_new flyout_tab_flyout responsive_slidedown" id="foryou_flyout" style="display: none;"> - <div class="popup_body popup_menu popup_menu_browse"> - <a class="popup_menu_item" href="https://store.steampowered.com/?snr=1_7_7_2300_12"> - Home </a> - <a class="popup_menu_item" href="https://store.steampowered.com/communityrecommendations/?snr=1_7_7_2300_12"> - Community Recommendations </a> - <a class="popup_menu_item" href="https://store.steampowered.com/recommended/?snr=1_7_7_2300_12"> - Recently Viewed </a> - <a class="popup_menu_item" href="https://store.steampowered.com/curators/?snr=1_7_7_2300_12"> - Steam Curators </a> - </div> - </div> - - <div class="store_labs_new"></div> - <div class="tab flyout_tab " id="noteworthy_tab" - data-flyout="noteworthy_flyout" data-flyout-align="left" data-flyout-valign="bottom" data-flyout-delay="300" data-panel="{"focusable":true}" > - <span class="pulldown"> - <a href="javascript:void(0);" class="pulldown_desktop">New & Noteworthy</a> - <a href="javascript:void(0);" class="pulldown_mobile">New & Noteworthy</a> - <span></span> - </span> - </div> - <div class="popup_block_new flyout_tab_flyout responsive_slidedown" id="noteworthy_flyout" style="display: none;"> - <div class="popup_body popup_menu popup_menu_browse"> - <a class="popup_menu_item" href="https://store.steampowered.com/search/?filter=topsellers&snr=1_7_7_2300_12"> - Top Sellers </a> - <a class="popup_menu_item" href="https://store.steampowered.com/explore/new/?snr=1_7_7_2300_12"> - New and Trending </a> - <a class="popup_menu_item" href="https://store.steampowered.com/specials/?snr=1_7_7_2300_12"> - Current Specials </a> - <a class="popup_menu_item" href="https://store.steampowered.com/newshub/?snr=1_7_7_2300_12"> - Recently Updated </a> - <a class="popup_menu_item" href="https://store.steampowered.com/explore/upcoming/?snr=1_7_7_2300_12"> - Popular Upcoming </a> - - <!-- - <div class="popup_menu_subheader">Sale Events - </div> - - <a class="popup_menu_item" href="https://store.steampowered.com/search/?specials=1&snr=1_7_7_2300_12"> - Weekly Specials - </a> - <a class="popup_menu_item" href="https://store.steampowered.com/sale/vr_specials/?snr=1_7_7_2300_12"> - Weekly VR Specials - </a> - --> - </div> - </div> - - <div class="tab flyout_tab " id="genre_tab" - data-flyout="genre_flyout" data-flyout-align="left" data-flyout-valign="bottom" data-flyout-align-to-element="foryou_tab" data-flyout-delay="300" - data-panel="{"focusable":true}" > - <span class="pulldown"> - <a class="pulldown_desktop" href="javascript:void(0);">Categories</a> - <a class="pulldown_mobile" href="javascript:void(0);">Categories</a> - <span></span> - </span> - </div> - <div class="popup_block_new flyout_tab_flyout responsive_slidedown" id="genre_flyout" style="display: none;"> - <div class="popup_body popup_menu_twocol_new"> - <div class="popup_menu popup_menu_browse" data-panel="{"maintainY":true,"flow-children":"column"}" > - - <div class="popup_menu_subheader responsive_hidden">Special Sections</div> - <a class="popup_menu_item" href="https://store.steampowered.com/genre/Free%20to%20Play/?snr=1_7_7_2300_12"> - Free to Play </a> - <a class="popup_menu_item" href="https://store.steampowered.com/demos/?snr=1_7_7_2300_12"> - <span>Demos</span> - </a> - <a class="popup_menu_item" href="https://store.steampowered.com/genre/Early%20Access/?snr=1_7_7_2300_12"> - Early Access </a> - - - <a class="popup_menu_item" href="https://store.steampowered.com/controller/?snr=1_7_7_2300_12"> - <span>Controller Friendly</span> - </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/remoteplay_hub/?snr=1_7_7_2300_12"> - <span>Remote Play</span> - </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/software/?snr=1_7_7_2300_12"> - Software </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/soundtracks?snr=1_7_7_2300_12"> - Soundtracks </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/vr/?snr=1_7_7_2300_12"> - <span>Virtual Reality</span> - </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/vrhardware/?snr=1_7_7_2300_12"> - <span>VR Hardware</span> - </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/steamdeck/?snr=1_7_7_2300_category-menu"> - <span>Steam Deck</span> - </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/macos?snr=1_7_7_2300_12"> - macOS </a> - <a class="popup_menu_item" href="https://store.steampowered.com/linux?snr=1_7_7_2300_12"> - SteamOS + Linux </a> - - <a class="popup_menu_item" href="https://store.steampowered.com/pccafe/?snr=1_7_7_2300_12"> - <span>For PC Cafés</span> - </a> - </div> - <div class="popup_menu popup_menu_browse leftborder" data-panel="{"maintainY":true,"flow-children":"column"}"> - <div class="popup_menu_subheader reduced_vspace"> - Genres - </div> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="action"> - Action </div> - <div class="popup_menu_subheader popup_genre_expand_header responsive_hidden" data-genre-group="action"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/action/?snr=1_7_7_2300_12"> - Action </a> - </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="action"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/action_rogue_like/?snr=1_7_7_2300_12">Action Rogue-Like</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/arcade_rhythm/?snr=1_7_7_2300_12">Arcade & Rhythm</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/action_beat_em_up/?snr=1_7_7_2300_12">Beat 'Em Up</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/fighting_martial_arts/?snr=1_7_7_2300_12">Fighting & Martial Arts</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/action_fps/?snr=1_7_7_2300_12">First-Person Shooter</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/action_run_jump/?snr=1_7_7_2300_12">Platformer & Runner</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/action_tps/?snr=1_7_7_2300_12">Third-Person Shooter</a> - </div> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="adventure_and_casual"> - Adventure & Casual </div> - <div class="popup_menu_subheader popup_genre_expand_header responsive_hidden" data-genre-group="adventure_and_casual"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/adventure_and_casual/?snr=1_7_7_2300_12"> - Adventure & Casual </a> - </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="adventure_and_casual"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/adventure/?snr=1_7_7_2300_12">Adventure</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/adventure_rpg/?snr=1_7_7_2300_12">Adventure RPG</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/casual/?snr=1_7_7_2300_12">Casual</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/metroidvania/?snr=1_7_7_2300_12">Metroidvania</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/puzzle_matching/?snr=1_7_7_2300_12">Puzzle</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/interactive_fiction/?snr=1_7_7_2300_12">Story-Rich</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/visual_novel/?snr=1_7_7_2300_12">Visual Novel</a> - </div> - </div> <div class="popup_menu popup_menu_browse " data-panel="{"maintainY":true,"flow-children":"column"}"> - <div class="popup_menu_subheader reduced_vspace responsive_hidden"> - <br> - </div> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="rpg"> - Role-Playing </div> - <div class="popup_menu_subheader popup_genre_expand_header responsive_hidden" data-genre-group="rpg"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/rpg/?snr=1_7_7_2300_12"> - Role-Playing </a> - </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="rpg"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/rpg_action/?snr=1_7_7_2300_12">Action RPG</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/adventure_rpg/?snr=1_7_7_2300_12">Adventure RPG</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/rpg_jrpg/?snr=1_7_7_2300_12">JRPG</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/rpg_party_based/?snr=1_7_7_2300_12">Party-Based</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/rogue_like_rogue_lite/?snr=1_7_7_2300_12">Rogue-Like</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/rpg_strategy_tactics/?snr=1_7_7_2300_12">Strategy RPG</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/rpg_turn_based/?snr=1_7_7_2300_12">Turn-Based</a> - </div> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="simulation"> - Simulation </div> - <div class="popup_menu_subheader popup_genre_expand_header responsive_hidden" data-genre-group="simulation"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/simulation/?snr=1_7_7_2300_12"> - Simulation </a> - </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="simulation"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sim_building_automation/?snr=1_7_7_2300_12">Building & Automation</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sim_business_tycoon/?snr=1_7_7_2300_12">Business & Tycoon</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sim_dating/?snr=1_7_7_2300_12">Dating</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sim_farming_crafting/?snr=1_7_7_2300_12">Farming & Crafting</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sim_life/?snr=1_7_7_2300_12">Life & Immersive</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sim_physics_sandbox/?snr=1_7_7_2300_12">Sandbox & Physics</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sim_space_flight/?snr=1_7_7_2300_12">Space & Flight</a> - </div> - </div> <div class="popup_menu popup_menu_browse " data-panel="{"maintainY":true,"flow-children":"column"}"> - <div class="popup_menu_subheader reduced_vspace responsive_hidden"> - <br> - </div> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="strategy"> - Strategy </div> - <div class="popup_menu_subheader popup_genre_expand_header responsive_hidden" data-genre-group="strategy"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/strategy/?snr=1_7_7_2300_12"> - Strategy </a> - </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="strategy"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/strategy_card_board/?snr=1_7_7_2300_12">Card & Board</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/strategy_cities_settlements/?snr=1_7_7_2300_12">City & Settlement</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/strategy_grand_4x/?snr=1_7_7_2300_12">Grand & 4X</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/strategy_military/?snr=1_7_7_2300_12">Military</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/strategy_real_time/?snr=1_7_7_2300_12">Real-Time Strategy</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/tower_defense/?snr=1_7_7_2300_12">Tower Defense</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/strategy_turn_based/?snr=1_7_7_2300_12">Turn-Based Strategy</a> - </div> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="sports_and_racing"> - Sports & Racing </div> - <div class="popup_menu_subheader popup_genre_expand_header responsive_hidden" data-genre-group="sports_and_racing"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sports_and_racing/?snr=1_7_7_2300_12"> - Sports & Racing </a> - </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="sports_and_racing"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sports/?snr=1_7_7_2300_12">All Sports</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sports_fishing_hunting/?snr=1_7_7_2300_12">Fishing & Hunting</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sports_individual/?snr=1_7_7_2300_12">Individual Sports</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/racing/?snr=1_7_7_2300_12">Racing</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/racing_sim/?snr=1_7_7_2300_12">Racing Sim</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sports_sim/?snr=1_7_7_2300_12">Sports Sim</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/sports_team/?snr=1_7_7_2300_12">Team Sports</a> - </div> - </div> <div class="popup_menu popup_menu_browse leftborder" data-panel="{"maintainY":true,"flow-children":"column"}"> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="themes"> - Themes </div> - <div class="popup_menu_subheader players popup_genre_expand_header responsive_hidden" > - Themes </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="themes"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/anime/?snr=1_7_7_2300_12">Anime</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/horror/?snr=1_7_7_2300_12">Horror</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/mystery_detective/?snr=1_7_7_2300_12">Mystery & Detective</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/exploration_open_world/?snr=1_7_7_2300_12">Open World</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/science_fiction/?snr=1_7_7_2300_12">Sci-Fi & Cyberpunk</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/space/?snr=1_7_7_2300_12">Space</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/survival/?snr=1_7_7_2300_12">Survival</a> - <div class="spacer responsive_hidden"></div> </div> - <div class="popup_menu_item popup_genre_expand_header nonresponsive_hidden" data-genre-group="social_and_players"> - Player Support </div> - <div class="popup_menu_subheader players popup_genre_expand_header responsive_hidden" > - Player Support </div> - <div class="popup_genre_expand_content responsive_hidden" data-genre-group="social_and_players"> - <a class="popup_menu_item" href="https://store.steampowered.com/category/multiplayer_coop/?snr=1_7_7_2300_12">Cooperative</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/multiplayer_lan/?snr=1_7_7_2300_12">LAN</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/multiplayer_local_party/?snr=1_7_7_2300_12">Local & Party</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/multiplayer_mmo/?snr=1_7_7_2300_12">MMO</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/multiplayer/?snr=1_7_7_2300_12">Multiplayer</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/multiplayer_online_competitive/?snr=1_7_7_2300_12">Online Competitive</a> - <a class="popup_menu_item" href="https://store.steampowered.com/category/singleplayer/?snr=1_7_7_2300_12">Singleplayer</a> - </div> - </div> </div> - </div> - - <a class="tab " href="https://store.steampowered.com/points/?snr=1_7_7_2300_12"> - <span>Points Shop</span> - </a> - - <a class="tab " href="https://store.steampowered.com/news/?snr=1_7_7_2300_12"> - <span>News</span> - </a> - - <a class="tab " href="https://store.steampowered.com/labs/?snr=1_7_7_2300_12"> - <span>Labs</span> - </a> - - <div class="search_flex_spacer"></div> - <div class="search_area"> - <div id="store_search"> - <form id="searchform" name="searchform" method="get" action="https://store.steampowered.com/search/" onsubmit="return SearchSuggestCheckTerm(this);"> - <input type="hidden" name="snr" value="1_7_7_2300_12" > - <div class="searchbox"> - <input id="store_nav_search_term" name="term" type="text" class="default" placeholder="search" size="22" autocomplete="off"> - <a href="#" id="store_search_link" onclick="var $Form = $J(this).parents('form'); $Form.submit(); return false;"><img src="https://store.cloudflare.steamstatic.com/public/images/blank.gif"></a> - </div> - </form> - </div> - <div id="searchterm_options" class="search_suggest popup_block_new" style="display: none;"> - <div class="popup_body" style="border-top: none;"> - <div id="search_suggestion_contents"> - </div> - </div> - </div> - </div> - - </div> - </div> - <div class="store_nav_rightcap"></div> - </div> - </div> - </div> - <script type="text/javascript"> - $J( function() { - BindAutoFlyoutEvents(); - - var $Window = $J(window); - var $Header = $J('#store_header'); - var $ResponsiveNavWindowShadeCtn = $J('#responsive_store_nav_ctn'); - var $ResponsiveNavOverlay = $J('#responsive_store_nav_overlay'); - var $ResponsiveNavOverlayCtn = $J('#responsive_store_nav_overlay_ctn'); - var $ResponsiveNavOverlayBottom = $J('#responsive_store_nav_overlay_bottom'); - var $HeaderWrapper; - $Window.on('Responsive_SmallScreenModeToggled.StoreMenu', function() { - var bUseSmallScreenMode = window.UseSmallScreenMode && window.UseSmallScreenMode(); - - if ( !$HeaderWrapper ) - $HeaderWrapper = $Header.wrap( $J('<div/>', {'class': 'responsive_store_nav_ctn_spacer'} ) ).parent(); - - if ( bUseSmallScreenMode ) - $ResponsiveNavWindowShadeCtn.append( $Header ); - else - $HeaderWrapper.append( $Header ); - - - if ( bUseSmallScreenMode ) - { - $Header.css( 'visibility', 'hidden' ); - $Header.show(); - - var nStartingScrollPosition = $J('#store_header').height(); - if ( $Window.scrollTop() < nStartingScrollPosition ) - $Window.scrollTop( nStartingScrollPosition ); - - $Header.css('visibility', 'visible'); - } - } ); - - window.setTimeout( function() { $J(window).trigger('Responsive_SmallScreenModeToggled.StoreMenu'); }, 0 ); - - if( $J('#searchform').length > 0 ) - { - var g_rgUserPreferences = { - excluded_tags : [], - excluded_content_descriptors : [3,4] }; - g_rgUserPreferences['use_store_query'] = 1; - EnableSearchSuggestions( $J('#searchform')[0].elements['term'], '1_7_7_2300', 'EC', 1, 'english', g_rgUserPreferences, '12479644' ); - } - - // make genre categories expand/collapse on mobile - $J(document).on( 'click', '.popup_genre_expand_header', function ( event ) { - if ( !UseSmallScreenMode() ) - return; - - event.preventDefault(); - var $Element = $J(this); - var $Target = $J('.popup_genre_expand_content[data-genre-group=' + $Element.data('genre-group') + ']' ); - if ( $Element.data('group-expanded') ) - { - $Target.slideUp(); - $Element.data( 'group-expanded', false ); - } - else - { - $Target.slideDown(); - $Element.data( 'group-expanded', true ); - } - }); - } ); - </script> - <script type="text/javascript"> - var g_AccountID = 0; - var g_sessionID = "927b7d1b2fdda742bee21f06"; - var g_ServerTime = 1630778989; - - $J( InitMiniprofileHovers ); - - - GStoreItemData.AddNavParams({ - __page_default: "1_7_7_2300", - storemenu_recommendedtags: "1_7_7_2300_17" }); - GDynamicStore.Init( 0, false, "", {"primary_language":null,"secondary_languages":null,"platform_windows":null,"platform_mac":null,"platform_linux":null,"hide_adult_content_violence":null,"hide_adult_content_sex":null,"timestamp_updated":null,"hide_store_broadcast":null,"review_score_preference":null,"timestamp_content_descriptor_preferences_updated":null}, 'EC', - {"bNoDefaultDescriptors":false} ); - GStoreItemData.SetCurrencyFormatter( function( nValueInCents, bWholeUnitsOnly ) { var fnBase = function( nValueInCents, bWholeUnitsOnly ) { var fmt = function( nValueInCents, bWholeUnitsOnly ) { var format = v_numberformat( nValueInCents / 100, bWholeUnitsOnly ? 0 : 2, ".", ","); return format; };var strNegativeSymbol = ''; if ( nValueInCents < 0 ) { strNegativeSymbol = '-'; nValueInCents = -nValueInCents; }return strNegativeSymbol + "$" + fmt( nValueInCents, bWholeUnitsOnly );}; return fnBase( nValueInCents, bWholeUnitsOnly ) + ' USD'; } ); - GStoreItemData.SetCurrencyMinPriceIncrement( 1 ); - </script> - - <div class="page_content"> - <div class="breadcrumbs"></div> - - <div class="search_labs_banner"> - <a href="https://store.steampowered.com/newshub/app/593110/view/1714119088658959583?snr=1_7_7_2300_147" class="search_labs_banner_link"> - <img src="https://cdn.cloudflare.steamstatic.com/steam/clusters/about_i18n_assets/about_i18n_assets_0/new_features_banner_english.png?t=1610987176" class="banner"> - <img src="https://cdn.cloudflare.steamstatic.com/store/labs/banner/labs_search_banner_hover.png" class="hover"> - </a> - </div> - - <h2 class="pageheader full"> - Specials </h2> - - <div class="termcontainer"> - <div id="termsnone"> - <div class="pagesubheader">All Products</div> - </div> - - <div class="searchtag" id="searchtag_tmpl" style="display: none"><img class="search_crouton_icon" /><span class="label"></span> <a href="#" class="btn"></a></div> - - </div> - </div> - -</div> -<form action="https://store.steampowered.com/search/" id="advsearchform" name="advsearchform" onSubmit="AjaxSearchResults(); return false;" method="GET"> -<!-- Main Background --> -<div class="page_content_ctn"> - - <div class="page_content" data-gpnav="columns"> - - <div class="leftcol large" data-gpnav="rows" > - - <div class="searchbar" data-gpnav="columns"> - <div class="sortbox"> - <div class="label">Sort by</div> - <div class="dselect_container" id="sort_by_dselect_container"> - <input id="sort_by" type="hidden" name="sort_by" value="_ASC" onchange="$J('sort_by').val(this.value); AjaxSearchResults(); "/> - <a class="trigger"id="sort_by_trigger" href="javascript:DSelectNoop();" onfocus="DSelectOnFocus( 'sort_by');" onblur="DSelectOnBlur( 'sort_by');" onclick="DSelectOnTriggerClick('sort_by');">Relevance</a> - <div class="dropcontainer"> - <ul class="dropdownhidden" id="sort_by_droplist"><li><a class="inactive_selection" tabindex="99999" href="javascript:DSelectNoop();" onmouseover="DHighlightItem( 'sort_by', 0, false );" id="_ASC" onclick="DHighlightItem( 'sort_by', 0, true );" >Relevance</a></li><li><a class="inactive_selection" tabindex="99999" href="javascript:DSelectNoop();" onmouseover="DHighlightItem( 'sort_by', 1, false );" id="Released_DESC" onclick="DHighlightItem( 'sort_by', 1, true );" >Release date</a></li><li><a class="inactive_selection" tabindex="99999" href="javascript:DSelectNoop();" onmouseover="DHighlightItem( 'sort_by', 2, false );" id="Name_ASC" onclick="DHighlightItem( 'sort_by', 2, true );" >Name</a></li><li><a class="inactive_selection" tabindex="99999" href="javascript:DSelectNoop();" onmouseover="DHighlightItem( 'sort_by', 3, false );" id="Price_ASC" onclick="DHighlightItem( 'sort_by', 3, true );" >Lowest Price</a></li><li><a class="inactive_selection" tabindex="99999" href="javascript:DSelectNoop();" onmouseover="DHighlightItem( 'sort_by', 4, false );" id="Price_DESC" onclick="DHighlightItem( 'sort_by', 4, true );" >Highest Price</a></li><li><a class="inactive_selection" tabindex="99999" href="javascript:DSelectNoop();" onmouseover="DHighlightItem( 'sort_by', 5, false );" id="Reviews_DESC" onclick="DHighlightItem( 'sort_by', 5, true );" >User Reviews</a></li></ul> - </div> - </div><script language="javascript">$J( function() { $J('#sort_by_dselect_container').on('keydown', HandleKeyDown ); });</script> </div> - <div class="searchbar_left"> - <input type="text" class="text" id="term" name="displayterm" data-gpnav="item" - onfocus="if(this.value=='enter search term or tag'){this.value=''}" - onchange="$('realterm').value= (this.value=='enter search term or tag') ? '' : this.value" - onblur="if(this.value==''){this.value='enter search term or tag';}" - value="enter search term or tag" maxlength="64"> - <input type="hidden" name="term" id="realterm" value=""> - <input type="hidden" name="hide_filtered_results_warning" id="hide_filtered_results_warning" value=""> - <input type="hidden" name="ignore_preferences" id="ignore_preferences" value=""> - <input type="hidden" name="force_infinite" id="force_infinite" value="" autocomplete="off" /> - <button type="submit" class="btnv6_blue_hoverfade btn_small" data-gpnav="item"> - <span>Search</span> - </button> - <div id="term_options" class="autocomplete" style="display: none;"></div> - </div> - <div style="clear: both;"></div> - </div> - - - <script type="text/javascript"> - new Ajax.Autocompleter( $('advsearchform').elements['displayterm'], 'term_options', 'https://store.steampowered.com/search/suggest', {frequency: 0.2, method: "get", paramName: 'term', parameters : 'cc=EC&realm=1&l=english&excluded_content_descriptors%5B0%5D=3&excluded_content_descriptors%5B1%5D=4', allowFreeEntry: true, afterUpdateElement:function() { $('realterm').value=$('term').value; AjaxSearchResults(); } }); - </script> - - <div id="search_results_filtered_warning_persistent" class="search_results_filtered_warning collapsed"></div> - <div id="search_results" class="search_results" data-gpnav="rows"> - -<script type = "text/javascript"> - $J( function() { - PopulateTagFacetData( [], [], false ); - }); -</script> - - <div class="search_results_count">1 result matches your search.</div> - <script> - g_strUnfilteredURL = 'https://store.steampowered.com/search/?maxprice=free&specials=1&ignore_preferences=1'; - </script> - -<div id = "search_result_container" > - - <div class="search_rule"></div> - - - <!-- Extra empty div to hack around IE7 layout bug --> - <div></div> - <!-- End Extra empty div --> - - <div id="search_resultsRows"> - -<!-- List Items --> - - <a href="https://store.steampowered.com/app/1314764/Battlefield_1_Shortcut_Kit_Infantry_Bundle/?snr=1_7_7_2300_150_1" - data-ds-appid="1314764" data-ds-itemkey="App_1314764" data-ds-tagids="[19,4667]" data-ds-descids="[2,5]" data-ds-crtrids="[36135791,36140373]" onmouseover="GameHover( this, event, 'global_hover', {"type":"app","id":1314764,"public":1,"v6":1} );" onmouseout="HideGameHover( this, event, 'global_hover' )" class="search_result_row ds_collapse_flag " - data-search-page="1" data-gpnav="item"> - <div class="col search_capsule"><img src="https://cdn.cloudflare.steamstatic.com/steam/apps/1314764/capsule_sm_120.jpg?t=1630429949" srcset="https://cdn.cloudflare.steamstatic.com/steam/apps/1314764/capsule_sm_120.jpg?t=1630429949 1x, https://cdn.cloudflare.steamstatic.com/steam/apps/1314764/capsule_231x87.jpg?t=1630429949 2x"></div> - <div class="responsive_search_name_combined"> - <div class="col search_name ellipsis"> - <span class="title">Battlefield 1 Shortcut Kit: Infantry Bundle</span> - <p> - <span class="platform_img win"></span> </p> - </div> - <div class="col search_released responsive_secondrow">11 Jun, 2020</div> - <div class="col search_reviewscore responsive_secondrow"> - </div> - - - <div class="col search_price_discount_combined responsive_secondrow" data-price-final="0"> - <div class="col search_discount responsive_secondrow"> - <span>-100%</span> - </div> - <div class="col search_price discounted responsive_secondrow"> - <span style="color: #888888;"><strike>$0.00</strike></span><br>Free </div> - </div> - </div> - - - <div style="clear: left;"></div> - </a> - <!-- End List Items --> - </div> - - <div class="search_pagination"> - <div class="search_pagination_left"> - showing 1 - 1 of 1 </div> - <div class="search_pagination_right"> - 1 </div> - <div style="clear: both;"></div> - </div> - - <div id="search_results_loading" style="display: none"> - <div class="LoadingWrapper"> - <div class="LoadingThrobber"> - <div class="Bar Bar1"></div> - <div class="Bar Bar2"></div> - <div class="Bar Bar3"></div> - </div> - <div class="LoadingText">Loading more content...</div> - </div> - </div> - - </div> - - </div> - </div> - <div class="rightcol small responsive_local_menu autocollapse_enabled" id="additional_search_options" data-gpnav="rows"> - - <div class="block search_collapse_block" data-collapse-name="price"> - <div class="block_header labs_block_header"> - <div>Narrow by Price</div> - <a href="https://store.steampowered.com/newshub/app/593110/view/1714119088658959583" onclick="event.stopPropagation();"><div class="labs_new"></div></a> </div> - <div class="block_content block_content_inner" > - <div class="range_container" style="margin-top: 8px;"> - <div class="range_container_inner"> - <input class="range_input" type="range" id="price_range" min="0" max="13" step="1" value="0"> - <input id="maxprice_input" type="hidden" name="maxprice" value="free"> - </div> - <div class="range_display" id="price_range_display"> - - </div> - <script type="text/javascript"> - var rgPriceStopData = [{"price":"free","label":"Free"},{"price":5,"label":"Under $5.00"},{"price":10,"label":"Under $10.00"},{"price":15,"label":"Under $15.00"},{"price":20,"label":"Under $20.00"},{"price":25,"label":"Under $25.00"},{"price":30,"label":"Under $30.00"},{"price":35,"label":"Under $35.00"},{"price":40,"label":"Under $40.00"},{"price":45,"label":"Under $45.00"},{"price":50,"label":"Under $50.00"},{"price":55,"label":"Under $55.00"},{"price":60,"label":"Under $60.00"},{"price":null,"label":"Any Price"}]; - $J( function() { - - $J('#price_range').on( 'input', function() { - $J('#price_range_display').text( rgPriceStopData[this.value].label ); - }).trigger('input'); - - $J('#price_range').on( 'change', function() { - $J('#maxprice_input').val( rgPriceStopData[this.value].price ); - AjaxSearchResults(); - }); - }) - - function UpdatePriceRangeControl( maxprice ) - { - var $Input = $J('#price_range'); - if ( !maxprice ) - $Input.val( 13 ); - else - { - for ( var i = 0; i < rgPriceStopData.length; i++ ) - { - if ( rgPriceStopData[i].price == maxprice ) - { - $Input.val( i ); - break; - } - } - } - $Input.trigger('input'); - } - </script> - </div> - - <div class="block_rule"></div> - - - <div class="tab_filter_control_row checked" data-param="specials" data-value="__toggle" data-loc="Special Offers" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include checked" data-param="specials" data-value="__toggle" data-loc="Special Offers" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Special Offers</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - </div> - </div> - - - - <div class="block search_collapse_block" data-collapse-name="tags"> - <div class="block_header labs_block_header"> - <div>Narrow by tag</div> - <a href="https://store.steampowered.com/newshub/app/593110/view/1714119088658959583" onclick="event.stopPropagation();"><div class="labs_new"></div></a> </div> - <div class="block_content block_content_inner" > - <div id="TagFilter_Container" style="max-height: 150px; overflow: hidden;"> - - <div class="tab_filter_control_row " data-param="tags" data-value="492" data-loc="Indie" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="492" data-loc="Indie" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Indie</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="492" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Indie" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="19" data-loc="Action" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="19" data-loc="Action" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Action</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="19" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Action" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="21" data-loc="Adventure" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="21" data-loc="Adventure" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Adventure</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="21" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Adventure" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="597" data-loc="Casual" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="597" data-loc="Casual" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Casual</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="597" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Casual" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="599" data-loc="Simulation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="599" data-loc="Simulation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Simulation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="599" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Simulation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9" data-loc="Strategy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9" data-loc="Strategy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Strategy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Strategy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="122" data-loc="RPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="122" data-loc="RPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">RPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="122" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="RPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4182" data-loc="Singleplayer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4182" data-loc="Singleplayer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Singleplayer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4182" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Singleplayer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="493" data-loc="Early Access" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="493" data-loc="Early Access" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Early Access</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="493" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Early Access" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="113" data-loc="Free to Play" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="113" data-loc="Free to Play" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Free to Play</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="113" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Free to Play" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3871" data-loc="2D" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3871" data-loc="2D" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">2D</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3871" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="2D" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4166" data-loc="Atmospheric" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4166" data-loc="Atmospheric" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Atmospheric</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4166" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Atmospheric" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4667" data-loc="Violent" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4667" data-loc="Violent" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Violent</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4667" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Violent" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="701" data-loc="Sports" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="701" data-loc="Sports" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sports</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="701" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sports" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="128" data-loc="Massively Multiplayer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="128" data-loc="Massively Multiplayer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Massively Multiplayer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="128" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Massively Multiplayer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3859" data-loc="Multiplayer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3859" data-loc="Multiplayer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Multiplayer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3859" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Multiplayer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1664" data-loc="Puzzle" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1664" data-loc="Puzzle" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Puzzle</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1664" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Puzzle" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1742" data-loc="Story Rich" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1742" data-loc="Story Rich" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Story Rich</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1742" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Story Rich" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4191" data-loc="3D" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4191" data-loc="3D" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">3D</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4191" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="3D" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1684" data-loc="Fantasy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1684" data-loc="Fantasy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Fantasy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1684" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Fantasy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3964" data-loc="Pixel Graphics" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3964" data-loc="Pixel Graphics" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Pixel Graphics</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3964" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Pixel Graphics" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="699" data-loc="Racing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="699" data-loc="Racing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Racing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="699" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Racing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4305" data-loc="Colorful" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4305" data-loc="Colorful" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Colorful</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4305" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Colorful" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4345" data-loc="Gore" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4345" data-loc="Gore" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Gore</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4345" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Gore" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6650" data-loc="Nudity" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6650" data-loc="Nudity" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Nudity</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6650" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Nudity" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="12095" data-loc="Sexual Content" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="12095" data-loc="Sexual Content" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sexual Content</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="12095" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sexual Content" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4085" data-loc="Anime" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4085" data-loc="Anime" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Anime</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4085" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Anime" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3834" data-loc="Exploration" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3834" data-loc="Exploration" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Exploration</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3834" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Exploration" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3839" data-loc="First-Person" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3839" data-loc="First-Person" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">First-Person</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3839" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="First-Person" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4726" data-loc="Cute" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4726" data-loc="Cute" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cute</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4726" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cute" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4136" data-loc="Funny" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4136" data-loc="Funny" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Funny</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4136" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Funny" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3942" data-loc="Sci-fi" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3942" data-loc="Sci-fi" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sci-fi</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3942" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sci-fi" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1773" data-loc="Arcade" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1773" data-loc="Arcade" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Arcade</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1773" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Arcade" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1774" data-loc="Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1774" data-loc="Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1774" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1756" data-loc="Great Soundtrack" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1756" data-loc="Great Soundtrack" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Great Soundtrack</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1756" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Great Soundtrack" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1667" data-loc="Horror" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1667" data-loc="Horror" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Horror</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1667" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Horror" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4004" data-loc="Retro" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4004" data-loc="Retro" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Retro</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4004" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Retro" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5350" data-loc="Family Friendly" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5350" data-loc="Family Friendly" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Family Friendly</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5350" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Family Friendly" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1695" data-loc="Open World" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1695" data-loc="Open World" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Open World</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1695" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Open World" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1685" data-loc="Co-op" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1685" data-loc="Co-op" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Co-op</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1685" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Co-op" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1654" data-loc="Relaxing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1654" data-loc="Relaxing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Relaxing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1654" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Relaxing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1625" data-loc="Platformer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1625" data-loc="Platformer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Platformer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1625" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Platformer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4106" data-loc="Action-Adventure" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4106" data-loc="Action-Adventure" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Action-Adventure</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4106" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Action-Adventure" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1662" data-loc="Survival" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1662" data-loc="Survival" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Survival</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1662" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Survival" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4026" data-loc="Difficult" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4026" data-loc="Difficult" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Difficult</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4026" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Difficult" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="21978" data-loc="VR" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="21978" data-loc="VR" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">VR</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="21978" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="VR" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7208" data-loc="Female Protagonist" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7208" data-loc="Female Protagonist" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Female Protagonist</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7208" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Female Protagonist" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1697" data-loc="Third Person" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1697" data-loc="Third Person" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Third Person</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1697" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Third Person" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1719" data-loc="Comedy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1719" data-loc="Comedy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Comedy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1719" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Comedy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3993" data-loc="Combat" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3993" data-loc="Combat" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Combat</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3993" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Combat" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3916" data-loc="Old School" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3916" data-loc="Old School" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Old School</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3916" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Old School" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1663" data-loc="FPS" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1663" data-loc="FPS" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">FPS</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1663" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="FPS" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1775" data-loc="PvP" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1775" data-loc="PvP" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">PvP</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1775" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="PvP" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4252" data-loc="Stylized" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4252" data-loc="Stylized" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Stylized</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4252" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Stylized" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3799" data-loc="Visual Novel" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3799" data-loc="Visual Novel" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Visual Novel</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3799" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Visual Novel" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3843" data-loc="Online Co-Op" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3843" data-loc="Online Co-Op" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Online Co-Op</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3843" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Online Co-Op" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6426" data-loc="Choices Matter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6426" data-loc="Choices Matter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Choices Matter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6426" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Choices Matter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4175" data-loc="Realistic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4175" data-loc="Realistic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Realistic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4175" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Realistic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3968" data-loc="Physics" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3968" data-loc="Physics" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Physics</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3968" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Physics" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4791" data-loc="Top-Down" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4791" data-loc="Top-Down" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Top-Down</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4791" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Top-Down" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7481" data-loc="Controller" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7481" data-loc="Controller" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Controller</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7481" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Controller" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3810" data-loc="Sandbox" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3810" data-loc="Sandbox" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sandbox</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3810" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sandbox" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4342" data-loc="Dark" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4342" data-loc="Dark" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dark</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4342" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dark" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5716" data-loc="Mystery" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5716" data-loc="Mystery" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mystery</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5716" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mystery" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4747" data-loc="Character Customization" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4747" data-loc="Character Customization" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Character Customization</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4747" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Character Customization" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4255" data-loc="Shoot 'Em Up" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4255" data-loc="Shoot 'Em Up" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Shoot 'Em Up</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4255" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Shoot 'Em Up" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4195" data-loc="Cartoony" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4195" data-loc="Cartoony" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cartoony</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4195" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cartoony" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1721" data-loc="Psychological Horror" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1721" data-loc="Psychological Horror" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Psychological Horror</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1721" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Psychological Horror" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="84" data-loc="Design & Illustration" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="84" data-loc="Design & Illustration" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Design & Illustration</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="84" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Design & Illustration" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1708" data-loc="Tactical" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1708" data-loc="Tactical" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tactical</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1708" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Tactical" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6971" data-loc="Multiple Endings" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6971" data-loc="Multiple Endings" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Multiple Endings</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6971" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Multiple Endings" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1755" data-loc="Space" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1755" data-loc="Space" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Space</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1755" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Space" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="87" data-loc="Utilities" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="87" data-loc="Utilities" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Utilities</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="87" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Utilities" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5379" data-loc="2D Platformer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5379" data-loc="2D Platformer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">2D Platformer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5379" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="2D Platformer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4094" data-loc="Minimalist" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4094" data-loc="Minimalist" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Minimalist</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4094" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Minimalist" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1643" data-loc="Building" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1643" data-loc="Building" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Building</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1643" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Building" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6730" data-loc="PvE" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6730" data-loc="PvE" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">PvE</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6730" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="PvE" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7368" data-loc="Local Multiplayer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7368" data-loc="Local Multiplayer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Local Multiplayer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7368" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Local Multiplayer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1698" data-loc="Point & Click" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1698" data-loc="Point & Click" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Point & Click</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1698" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Point & Click" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="12472" data-loc="Management" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="12472" data-loc="Management" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Management</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="12472" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Management" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7250" data-loc="Linear" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7250" data-loc="Linear" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Linear</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7250" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Linear" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4295" data-loc="Futuristic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4295" data-loc="Futuristic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Futuristic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4295" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Futuristic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1677" data-loc="Turn-Based" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1677" data-loc="Turn-Based" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Turn-Based</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1677" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Turn-Based" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4231" data-loc="Action RPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4231" data-loc="Action RPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Action RPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4231" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Action RPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4057" data-loc="Magic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4057" data-loc="Magic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Magic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4057" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Magic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7743" data-loc="1980s" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7743" data-loc="1980s" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">1980s</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7743" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="1980s" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1702" data-loc="Crafting" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1702" data-loc="Crafting" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Crafting</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1702" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Crafting" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1036" data-loc="Education" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1036" data-loc="Education" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Education</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1036" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Education" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4711" data-loc="Replay Value" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4711" data-loc="Replay Value" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Replay Value</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4711" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Replay Value" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6815" data-loc="Hand-drawn" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6815" data-loc="Hand-drawn" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hand-drawn</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6815" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hand-drawn" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3798" data-loc="Side Scroller" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3798" data-loc="Side Scroller" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Side Scroller</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3798" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Side Scroller" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5125" data-loc="Procedural Generation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5125" data-loc="Procedural Generation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Procedural Generation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5125" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Procedural Generation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4562" data-loc="Cartoon" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4562" data-loc="Cartoon" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cartoon</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4562" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cartoon" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5537" data-loc="Puzzle Platformer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5537" data-loc="Puzzle Platformer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Puzzle Platformer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5537" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Puzzle Platformer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8945" data-loc="Resource Management" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8945" data-loc="Resource Management" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Resource Management</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8945" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Resource Management" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3978" data-loc="Survival Horror" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3978" data-loc="Survival Horror" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Survival Horror</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3978" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Survival Horror" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4172" data-loc="Medieval" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4172" data-loc="Medieval" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Medieval</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4172" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Medieval" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5611" data-loc="Mature" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5611" data-loc="Mature" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mature</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5611" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mature" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1659" data-loc="Zombies" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1659" data-loc="Zombies" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Zombies</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1659" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Zombies" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3841" data-loc="Local Co-Op" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3841" data-loc="Local Co-Op" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Local Co-Op</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3841" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Local Co-Op" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1678" data-loc="War" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1678" data-loc="War" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">War</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1678" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="War" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1741" data-loc="Turn-Based Strategy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1741" data-loc="Turn-Based Strategy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Turn-Based Strategy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1741" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Turn-Based Strategy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1716" data-loc="Roguelike" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1716" data-loc="Roguelike" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Roguelike</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1716" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Roguelike" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4325" data-loc="Turn-Based Combat" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4325" data-loc="Turn-Based Combat" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Turn-Based Combat</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4325" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Turn-Based Combat" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4604" data-loc="Dark Fantasy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4604" data-loc="Dark Fantasy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dark Fantasy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4604" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dark Fantasy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6129" data-loc="Logic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6129" data-loc="Logic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Logic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6129" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Logic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1646" data-loc="Hack and Slash" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1646" data-loc="Hack and Slash" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hack and Slash</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1646" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hack and Slash" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5984" data-loc="Drama" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5984" data-loc="Drama" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Drama</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5984" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Drama" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10397" data-loc="Memes" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10397" data-loc="Memes" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Memes</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10397" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Memes" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3835" data-loc="Post-apocalyptic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3835" data-loc="Post-apocalyptic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Post-apocalyptic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3835" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Post-apocalyptic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4947" data-loc="Romance" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4947" data-loc="Romance" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Romance</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4947" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Romance" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3987" data-loc="Historical" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3987" data-loc="Historical" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Historical</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3987" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Historical" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5395" data-loc="3D Platformer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5395" data-loc="3D Platformer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">3D Platformer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5395" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="3D Platformer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4486" data-loc="Choose Your Own Adventure" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4486" data-loc="Choose Your Own Adventure" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Choose Your Own Adventure</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4486" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Choose Your Own Adventure" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7332" data-loc="Base Building" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7332" data-loc="Base Building" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Base Building</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7332" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Base Building" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3959" data-loc="Roguelite" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3959" data-loc="Roguelite" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Roguelite</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3959" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Roguelite" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4434" data-loc="JRPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4434" data-loc="JRPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">JRPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4434" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="JRPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1038" data-loc="Web Publishing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1038" data-loc="Web Publishing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Web Publishing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1038" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Web Publishing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1693" data-loc="Classic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1693" data-loc="Classic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Classic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1693" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Classic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9551" data-loc="Dating Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9551" data-loc="Dating Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dating Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9551" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dating Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5900" data-loc="Walking Simulator" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5900" data-loc="Walking Simulator" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Walking Simulator</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5900" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Walking Simulator" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="14139" data-loc="Turn-Based Tactics" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="14139" data-loc="Turn-Based Tactics" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Turn-Based Tactics</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="14139" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Turn-Based Tactics" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1687" data-loc="Stealth" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1687" data-loc="Stealth" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Stealth</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1687" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Stealth" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1710" data-loc="Surreal" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1710" data-loc="Surreal" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Surreal</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1710" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Surreal" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10695" data-loc="Party-Based RPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10695" data-loc="Party-Based RPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Party-Based RPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10695" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Party-Based RPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="11014" data-loc="Interactive Fiction" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="11014" data-loc="Interactive Fiction" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Interactive Fiction</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="11014" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Interactive Fiction" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1734" data-loc="Fast-Paced" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1734" data-loc="Fast-Paced" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Fast-Paced</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1734" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Fast-Paced" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5094" data-loc="Narration" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5094" data-loc="Narration" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Narration</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5094" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Narration" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4700" data-loc="Movie" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4700" data-loc="Movie" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Movie</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4700" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Movie" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1720" data-loc="Dungeon Crawler" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1720" data-loc="Dungeon Crawler" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dungeon Crawler</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1720" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dungeon Crawler" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4234" data-loc="Short" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4234" data-loc="Short" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Short</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4234" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Short" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1738" data-loc="Hidden Object" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1738" data-loc="Hidden Object" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hidden Object</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1738" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hidden Object" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4168" data-loc="Military" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4168" data-loc="Military" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Military</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4168" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Military" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4885" data-loc="Bullet Hell" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4885" data-loc="Bullet Hell" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Bullet Hell</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4885" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Bullet Hell" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5154" data-loc="Score Attack" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5154" data-loc="Score Attack" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Score Attack</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5154" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Score Attack" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8013" data-loc="Software" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8013" data-loc="Software" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Software</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8013" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Software" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="872" data-loc="Animation & Modeling" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="872" data-loc="Animation & Modeling" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Animation & Modeling</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="872" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Animation & Modeling" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3814" data-loc="Third-Person Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3814" data-loc="Third-Person Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Third-Person Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3814" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Third-Person Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5608" data-loc="Emotional" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5608" data-loc="Emotional" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Emotional</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5608" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Emotional" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6691" data-loc="1990's" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6691" data-loc="1990's" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">1990's</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6691" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="1990's" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5711" data-loc="Team-Based" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5711" data-loc="Team-Based" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Team-Based</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5711" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Team-Based" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1676" data-loc="RTS" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1676" data-loc="RTS" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">RTS</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1676" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="RTS" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5411" data-loc="Beautiful" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5411" data-loc="Beautiful" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Beautiful</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5411" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Beautiful" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9130" data-loc="Hentai" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9130" data-loc="Hentai" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hentai</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9130" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hentai" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4637" data-loc="Top-Down Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4637" data-loc="Top-Down Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Top-Down Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4637" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Top-Down Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5851" data-loc="Isometric" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5851" data-loc="Isometric" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Isometric</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5851" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Isometric" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5752" data-loc="Robots" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5752" data-loc="Robots" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Robots</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5752" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Robots" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4115" data-loc="Cyberpunk" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4115" data-loc="Cyberpunk" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cyberpunk</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4115" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cyberpunk" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="30358" data-loc="Nature" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="30358" data-loc="Nature" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Nature</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="30358" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Nature" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="13782" data-loc="Experimental" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="13782" data-loc="Experimental" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Experimental</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="13782" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Experimental" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9204" data-loc="Immersive Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9204" data-loc="Immersive Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Immersive Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9204" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Immersive Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5923" data-loc="Dark Humor" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5923" data-loc="Dark Humor" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dark Humor</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5923" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dark Humor" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="31275" data-loc="Text-Based" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="31275" data-loc="Text-Based" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Text-Based</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="31275" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Text-Based" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1673" data-loc="Aliens" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1673" data-loc="Aliens" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Aliens</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1673" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Aliens" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4975" data-loc="2.5D" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4975" data-loc="2.5D" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">2.5D</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4975" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="2.5D" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1743" data-loc="Fighting" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1743" data-loc="Fighting" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Fighting</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1743" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Fighting" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1644" data-loc="Driving" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1644" data-loc="Driving" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Driving</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1644" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Driving" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1621" data-loc="Music" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1621" data-loc="Music" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Music</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1621" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Music" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5577" data-loc="RPGMaker" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5577" data-loc="RPGMaker" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">RPGMaker</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5577" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="RPGMaker" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="15172" data-loc="Conversation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="15172" data-loc="Conversation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Conversation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="15172" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Conversation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4145" data-loc="Cinematic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4145" data-loc="Cinematic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cinematic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4145" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cinematic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4695" data-loc="Economy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4695" data-loc="Economy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Economy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4695" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Economy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4840" data-loc="4 Player Local" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4840" data-loc="4 Player Local" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">4 Player Local</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4840" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="4 Player Local" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4400" data-loc="Abstract" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4400" data-loc="Abstract" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Abstract</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4400" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Abstract" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1666" data-loc="Card Game" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1666" data-loc="Card Game" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Card Game</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1666" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Card Game" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1027" data-loc="Audio Production" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1027" data-loc="Audio Production" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Audio Production</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1027" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Audio Production" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="44868" data-loc="LGBTQ+" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="44868" data-loc="LGBTQ+" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">LGBTQ+</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="44868" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="LGBTQ+" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8369" data-loc="Investigation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8369" data-loc="Investigation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Investigation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8369" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Investigation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="42804" data-loc="Action Roguelike" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="42804" data-loc="Action Roguelike" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Action Roguelike</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="42804" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Action Roguelike" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="15045" data-loc="Flight" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="15045" data-loc="Flight" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Flight</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="15045" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Flight" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6276" data-loc="Inventory Management" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6276" data-loc="Inventory Management" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Inventory Management</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6276" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Inventory Management" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1759" data-loc="Perma Death" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1759" data-loc="Perma Death" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Perma Death</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1759" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Perma Death" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6869" data-loc="Nonlinear" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6869" data-loc="Nonlinear" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Nonlinear</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6869" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Nonlinear" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="12057" data-loc="Tutorial" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="12057" data-loc="Tutorial" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tutorial</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="12057" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Tutorial" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7948" data-loc="Soundtrack" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7948" data-loc="Soundtrack" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Soundtrack</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7948" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Soundtrack" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3878" data-loc="Competitive" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3878" data-loc="Competitive" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Competitive</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3878" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Competitive" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1770" data-loc="Board Game" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1770" data-loc="Board Game" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Board Game</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1770" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Board Game" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1669" data-loc="Moddable" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1669" data-loc="Moddable" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Moddable</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1669" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Moddable" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5547" data-loc="Arena Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5547" data-loc="Arena Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Arena Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5547" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Arena Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5186" data-loc="Psychological" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5186" data-loc="Psychological" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Psychological</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5186" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Psychological" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3813" data-loc="Real Time Tactics" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3813" data-loc="Real Time Tactics" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Real Time Tactics</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3813" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Real Time Tactics" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5613" data-loc="Detective" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5613" data-loc="Detective" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Detective</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5613" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Detective" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="784" data-loc="Video Production" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="784" data-loc="Video Production" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Video Production</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="784" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Video Production" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1645" data-loc="Tower Defense" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1645" data-loc="Tower Defense" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tower Defense</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1645" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Tower Defense" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="17305" data-loc="Strategy RPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="17305" data-loc="Strategy RPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Strategy RPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="17305" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Strategy RPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9541" data-loc="Demons" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9541" data-loc="Demons" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Demons</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9541" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Demons" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7926" data-loc="Artificial Intelligence" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7926" data-loc="Artificial Intelligence" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Artificial Intelligence</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7926" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Artificial Intelligence" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4064" data-loc="Thriller" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4064" data-loc="Thriller" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Thriller</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4064" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Thriller" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="17389" data-loc="Tabletop" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="17389" data-loc="Tabletop" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tabletop</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="17389" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Tabletop" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4328" data-loc="City Builder" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4328" data-loc="City Builder" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">City Builder</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4328" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="City Builder" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5673" data-loc="Modern" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5673" data-loc="Modern" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Modern</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5673" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Modern" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5363" data-loc="Destruction" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5363" data-loc="Destruction" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Destruction</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5363" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Destruction" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="24904" data-loc="NSFW" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="24904" data-loc="NSFW" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">NSFW</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="24904" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="NSFW" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4158" data-loc="Beat 'em up" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4158" data-loc="Beat 'em up" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Beat 'em up</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4158" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Beat 'em up" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="379975" data-loc="Clicker" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="379975" data-loc="Clicker" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Clicker</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="379975" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Clicker" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3854" data-loc="Lore-Rich" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3854" data-loc="Lore-Rich" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Lore-Rich</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3854" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Lore-Rich" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10235" data-loc="Life Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10235" data-loc="Life Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Life Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10235" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Life Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5030" data-loc="Dystopian " data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5030" data-loc="Dystopian " data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dystopian </span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5030" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dystopian " data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4236" data-loc="Loot" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4236" data-loc="Loot" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Loot</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4236" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Loot" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1714" data-loc="Psychedelic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1714" data-loc="Psychedelic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Psychedelic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1714" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Psychedelic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="16689" data-loc="Time Management" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="16689" data-loc="Time Management" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Time Management</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="16689" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Time Management" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1628" data-loc="Metroidvania" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1628" data-loc="Metroidvania" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Metroidvania</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1628" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Metroidvania" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="21725" data-loc="Tactical RPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="21725" data-loc="Tactical RPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tactical RPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="21725" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Tactical RPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4598" data-loc="Alternate History" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4598" data-loc="Alternate History" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Alternate History</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4598" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Alternate History" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3877" data-loc="Precision Platformer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3877" data-loc="Precision Platformer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Precision Platformer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3877" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Precision Platformer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10808" data-loc="Supernatural" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10808" data-loc="Supernatural" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Supernatural</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10808" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Supernatural" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8122" data-loc="Level Editor" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8122" data-loc="Level Editor" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Level Editor</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8122" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Level Editor" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4684" data-loc="Wargame" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4684" data-loc="Wargame" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Wargame</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4684" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Wargame" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1754" data-loc="MMORPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1754" data-loc="MMORPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">MMORPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1754" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="MMORPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3955" data-loc="Character Action Game" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3955" data-loc="Character Action Game" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Character Action Game</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3955" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Character Action Game" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1751" data-loc="Comic Book" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1751" data-loc="Comic Book" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Comic Book</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1751" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Comic Book" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="13906" data-loc="Game Development" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="13906" data-loc="Game Development" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Game Development</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="13906" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Game Development" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4150" data-loc="World War II" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4150" data-loc="World War II" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">World War II</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4150" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="World War II" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6378" data-loc="Crime" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6378" data-loc="Crime" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Crime</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6378" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Crime" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4036" data-loc="Parkour" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4036" data-loc="Parkour" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Parkour</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4036" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Parkour" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="19995" data-loc="Dark Comedy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="19995" data-loc="Dark Comedy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dark Comedy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="19995" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dark Comedy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="29482" data-loc="Souls-like" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="29482" data-loc="Souls-like" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Souls-like</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="29482" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Souls-like" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="16094" data-loc="Mythology" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="16094" data-loc="Mythology" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mythology</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="16094" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mythology" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4190" data-loc="Addictive" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4190" data-loc="Addictive" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Addictive</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4190" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Addictive" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4736" data-loc="2D Fighter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4736" data-loc="2D Fighter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">2D Fighter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4736" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="2D Fighter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8666" data-loc="Runner" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8666" data-loc="Runner" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Runner</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8666" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Runner" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7569" data-loc="Grid-Based Movement" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7569" data-loc="Grid-Based Movement" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Grid-Based Movement</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7569" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Grid-Based Movement" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4758" data-loc="Twin Stick Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4758" data-loc="Twin Stick Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Twin Stick Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4758" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Twin Stick Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="15277" data-loc="Philosophical" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="15277" data-loc="Philosophical" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Philosophical</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="15277" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Philosophical" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1100687" data-loc="Automobile Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1100687" data-loc="Automobile Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Automobile Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1100687" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Automobile Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4474" data-loc="CRPG" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4474" data-loc="CRPG" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">CRPG</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4474" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="CRPG" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1445" data-loc="Software Training" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1445" data-loc="Software Training" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Software Training</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1445" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Software Training" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5794" data-loc="Science" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5794" data-loc="Science" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Science</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5794" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Science" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4508" data-loc="Co-op Campaign" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4508" data-loc="Co-op Campaign" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Co-op Campaign</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4508" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Co-op Campaign" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4364" data-loc="Grand Strategy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4364" data-loc="Grand Strategy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Grand Strategy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4364" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Grand Strategy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5228" data-loc="Blood" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5228" data-loc="Blood" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Blood</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5228" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Blood" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4155" data-loc="Class-Based" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4155" data-loc="Class-Based" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Class-Based</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4155" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Class-Based" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="16598" data-loc="Space Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="16598" data-loc="Space Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Space Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="16598" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Space Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1752" data-loc="Rhythm" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1752" data-loc="Rhythm" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Rhythm</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1752" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Rhythm" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7432" data-loc="Lovecraftian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7432" data-loc="Lovecraftian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Lovecraftian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7432" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Lovecraftian" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4608" data-loc="Swordplay" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4608" data-loc="Swordplay" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Swordplay</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4608" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Swordplay" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5765" data-loc="Gun Customization" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5765" data-loc="Gun Customization" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Gun Customization</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5765" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Gun Customization" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10816" data-loc="Split Screen" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10816" data-loc="Split Screen" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Split Screen</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10816" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Split Screen" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7478" data-loc="Illuminati" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7478" data-loc="Illuminati" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Illuminati</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7478" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Illuminati" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1100689" data-loc="Open World Survival Craft" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1100689" data-loc="Open World Survival Craft" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Open World Survival Craft</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1100689" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Open World Survival Craft" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="176981" data-loc="Battle Royale" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="176981" data-loc="Battle Royale" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Battle Royale</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="176981" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Battle Royale" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5652" data-loc="Collectathon" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5652" data-loc="Collectathon" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Collectathon</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5652" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Collectathon" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4046" data-loc="Dragons" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4046" data-loc="Dragons" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dragons</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4046" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dragons" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5055" data-loc="eSports" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5055" data-loc="eSports" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">eSports</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5055" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="eSports" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="29363" data-loc="3D Vision" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="29363" data-loc="3D Vision" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">3D Vision</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="29363" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="3D Vision" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4835" data-loc="6DOF" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4835" data-loc="6DOF" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">6DOF</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4835" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="6DOF" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="17894" data-loc="Cats" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="17894" data-loc="Cats" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cats</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="17894" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cats" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1665" data-loc="Match 3" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1665" data-loc="Match 3" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Match 3</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1665" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Match 3" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4878" data-loc="Parody " data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4878" data-loc="Parody " data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Parody </span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4878" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Parody " data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="32322" data-loc="Deckbuilding" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="32322" data-loc="Deckbuilding" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Deckbuilding</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="32322" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Deckbuilding" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="11104" data-loc="Vehicular Combat" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="11104" data-loc="Vehicular Combat" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Vehicular Combat</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="11104" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Vehicular Combat" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="13190" data-loc="America" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="13190" data-loc="America" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">America</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="13190" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="America" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6052" data-loc="Noir" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6052" data-loc="Noir" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Noir</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6052" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Noir" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="615955" data-loc="Idler" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="615955" data-loc="Idler" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Idler</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="615955" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Idler" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1651" data-loc="Satire" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1651" data-loc="Satire" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Satire</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1651" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Satire" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4242" data-loc="Episodic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4242" data-loc="Episodic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Episodic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4242" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Episodic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4161" data-loc="Real-Time" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4161" data-loc="Real-Time" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Real-Time</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4161" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Real-Time" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="11123" data-loc="Mouse only" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="11123" data-loc="Mouse only" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mouse only</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="11123" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mouse only" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5796" data-loc="Bullet Time" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5796" data-loc="Bullet Time" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Bullet Time</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5796" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Bullet Time" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5372" data-loc="Conspiracy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5372" data-loc="Conspiracy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Conspiracy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5372" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Conspiracy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3965" data-loc="Epic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3965" data-loc="Epic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Epic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3965" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Epic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="791774" data-loc="Card Battler" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="791774" data-loc="Card Battler" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Card Battler</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="791774" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Card Battler" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1732" data-loc="Voxel" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1732" data-loc="Voxel" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Voxel</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1732" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Voxel" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7782" data-loc="Cult Classic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7782" data-loc="Cult Classic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cult Classic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7782" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cult Classic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1777" data-loc="Steampunk" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1777" data-loc="Steampunk" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Steampunk</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1777" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Steampunk" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4853" data-loc="Political" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4853" data-loc="Political" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Political</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4853" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Political" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4202" data-loc="Trading" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4202" data-loc="Trading" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Trading</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4202" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Trading" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4845" data-loc="Capitalism" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4845" data-loc="Capitalism" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Capitalism</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4845" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Capitalism" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="809" data-loc="Photo Editing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="809" data-loc="Photo Editing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Photo Editing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="809" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Photo Editing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6506" data-loc="3D Fighter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6506" data-loc="3D Fighter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">3D Fighter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6506" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="3D Fighter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6625" data-loc="Time Manipulation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6625" data-loc="Time Manipulation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Time Manipulation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6625" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Time Manipulation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="220585" data-loc="Colony Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="220585" data-loc="Colony Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Colony Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="220585" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Colony Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9564" data-loc="Hunting" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9564" data-loc="Hunting" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hunting</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9564" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hunting" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4821" data-loc="Mechs" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4821" data-loc="Mechs" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mechs</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4821" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mechs" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5981" data-loc="Mining" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5981" data-loc="Mining" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mining</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5981" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mining" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="198631" data-loc="Mystery Dungeon" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="198631" data-loc="Mystery Dungeon" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mystery Dungeon</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="198631" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mystery Dungeon" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3952" data-loc="Gothic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3952" data-loc="Gothic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Gothic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3952" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Gothic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="13276" data-loc="Tanks" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="13276" data-loc="Tanks" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tanks</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="13276" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Tanks" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10679" data-loc="Time Travel" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10679" data-loc="Time Travel" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Time Travel</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10679" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Time Travel" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1718" data-loc="MOBA" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1718" data-loc="MOBA" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">MOBA</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1718" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="MOBA" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="255534" data-loc="Automation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="255534" data-loc="Automation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Automation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="255534" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Automation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="21006" data-loc="Underground" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="21006" data-loc="Underground" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Underground</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="21006" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Underground" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="31579" data-loc="Otome" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="31579" data-loc="Otome" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Otome</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="31579" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Otome" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5708" data-loc="Remake" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5708" data-loc="Remake" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Remake</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5708" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Remake" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4754" data-loc="Politics" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4754" data-loc="Politics" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Politics</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4754" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Politics" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1688" data-loc="Ninja" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1688" data-loc="Ninja" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Ninja</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1688" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Ninja" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5502" data-loc="Hacking" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5502" data-loc="Hacking" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hacking</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5502" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hacking" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9592" data-loc="Dynamic Narration" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9592" data-loc="Dynamic Narration" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dynamic Narration</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9592" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dynamic Narration" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1681" data-loc="Pirates" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1681" data-loc="Pirates" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Pirates</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1681" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Pirates" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="22602" data-loc="Agriculture" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="22602" data-loc="Agriculture" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Agriculture</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="22602" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Agriculture" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="87918" data-loc="Farming Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="87918" data-loc="Farming Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Farming Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="87918" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Farming Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6915" data-loc="Martial Arts" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6915" data-loc="Martial Arts" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Martial Arts</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6915" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Martial Arts" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="24003" data-loc="Word Game" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="24003" data-loc="Word Game" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Word Game</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="24003" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Word Game" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4559" data-loc="Quick-Time Events" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4559" data-loc="Quick-Time Events" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Quick-Time Events</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4559" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Quick-Time Events" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5300" data-loc="God Game" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5300" data-loc="God Game" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">God Game</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5300" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="God Game" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5179" data-loc="Cold War" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5179" data-loc="Cold War" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cold War</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5179" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cold War" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1717" data-loc="Hex Grid" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1717" data-loc="Hex Grid" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hex Grid</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1717" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hex Grid" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1670" data-loc="4X" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1670" data-loc="4X" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">4X</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1670" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="4X" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1638" data-loc="Dog" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1638" data-loc="Dog" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dog</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1638" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dog" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="620519" data-loc="Hero Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="620519" data-loc="Hero Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hero Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="620519" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hero Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4777" data-loc="Spectacle fighter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4777" data-loc="Spectacle fighter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Spectacle fighter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4777" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Spectacle fighter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4102" data-loc="Combat Racing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4102" data-loc="Combat Racing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Combat Racing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4102" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Combat Racing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="18594" data-loc="FMV" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="18594" data-loc="FMV" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">FMV</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="18594" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="FMV" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7107" data-loc="Real-Time with Pause" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7107" data-loc="Real-Time with Pause" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Real-Time with Pause</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7107" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Real-Time with Pause" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1671" data-loc="Superhero" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1671" data-loc="Superhero" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Superhero</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1671" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Superhero" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="15564" data-loc="Fishing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="15564" data-loc="Fishing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Fishing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="15564" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Fishing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5160" data-loc="Dinosaurs" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5160" data-loc="Dinosaurs" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dinosaurs</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5160" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dinosaurs" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9271" data-loc="Trading Card Game" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9271" data-loc="Trading Card Game" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Trading Card Game</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9271" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Trading Card Game" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="17770" data-loc="Asynchronous Multiplayer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="17770" data-loc="Asynchronous Multiplayer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Asynchronous Multiplayer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="17770" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Asynchronous Multiplayer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="13070" data-loc="Solitaire" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="13070" data-loc="Solitaire" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Solitaire</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="13070" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Solitaire" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="353880" data-loc="Looter Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="353880" data-loc="Looter Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Looter Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="353880" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Looter Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5432" data-loc="Programming" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5432" data-loc="Programming" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Programming</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5432" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Programming" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4376" data-loc="Assassin" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4376" data-loc="Assassin" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Assassin</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4376" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Assassin" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1616" data-loc="Trains" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1616" data-loc="Trains" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Trains</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1616" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Trains" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5153" data-loc="Kickstarter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5153" data-loc="Kickstarter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Kickstarter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5153" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Kickstarter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9157" data-loc="Underwater" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9157" data-loc="Underwater" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Underwater</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9157" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Underwater" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="916648" data-loc="Creature Collector" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="916648" data-loc="Creature Collector" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Creature Collector</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="916648" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Creature Collector" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1647" data-loc="Western" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1647" data-loc="Western" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Western</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1647" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Western" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6910" data-loc="Naval" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6910" data-loc="Naval" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Naval</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6910" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Naval" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3934" data-loc="Immersive" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3934" data-loc="Immersive" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Immersive</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3934" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Immersive" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8093" data-loc="Minigames" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8093" data-loc="Minigames" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Minigames</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8093" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Minigames" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4018" data-loc="Vampire" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4018" data-loc="Vampire" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Vampire</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4018" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Vampire" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1680" data-loc="Heist" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1680" data-loc="Heist" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Heist</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1680" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Heist" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1649" data-loc="GameMaker" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1649" data-loc="GameMaker" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">GameMaker</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1649" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="GameMaker" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="180368" data-loc="Faith" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="180368" data-loc="Faith" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Faith</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="180368" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Faith" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="25085" data-loc="Touch-Friendly" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="25085" data-loc="Touch-Friendly" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Touch-Friendly</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="25085" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Touch-Friendly" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9994" data-loc="Experience" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9994" data-loc="Experience" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Experience</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9994" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Experience" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="26921" data-loc="Political Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="26921" data-loc="Political Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Political Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="26921" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Political Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="13382" data-loc="Archery" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="13382" data-loc="Archery" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Archery</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="13382" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Archery" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7702" data-loc="Narrative" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7702" data-loc="Narrative" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Narrative</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7702" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Narrative" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5348" data-loc="Mod" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5348" data-loc="Mod" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mod</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5348" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mod" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1730" data-loc="Sokoban" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1730" data-loc="Sokoban" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sokoban</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1730" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sokoban" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7108" data-loc="Party" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7108" data-loc="Party" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Party</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7108" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Party" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6310" data-loc="Diplomacy" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6310" data-loc="Diplomacy" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Diplomacy</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6310" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Diplomacy" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7178" data-loc="Party Game" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7178" data-loc="Party Game" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Party Game</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7178" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Party Game" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="51306" data-loc="Foreign" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="51306" data-loc="Foreign" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Foreign</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="51306" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Foreign" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5230" data-loc="Sequel" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5230" data-loc="Sequel" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sequel</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5230" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sequel" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="14153" data-loc="Dungeons & Dragons" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="14153" data-loc="Dungeons & Dragons" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dungeons & Dragons</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="14153" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Dungeons & Dragons" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3920" data-loc="Cooking" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3920" data-loc="Cooking" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cooking</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3920" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cooking" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="15339" data-loc="Documentary" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="15339" data-loc="Documentary" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Documentary</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="15339" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Documentary" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4994" data-loc="Naval Combat" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4994" data-loc="Naval Combat" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Naval Combat</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4994" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Naval Combat" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10383" data-loc="Transportation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10383" data-loc="Transportation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Transportation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10383" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Transportation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="9803" data-loc="Snow" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="9803" data-loc="Snow" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Snow</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="9803" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Snow" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8253" data-loc="Music-Based Procedural Generation" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8253" data-loc="Music-Based Procedural Generation" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Music-Based Procedural Generation</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8253" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Music-Based Procedural Generation" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="13577" data-loc="Sailing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="13577" data-loc="Sailing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sailing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="13577" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sailing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1084988" data-loc="Auto Battler" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1084988" data-loc="Auto Battler" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Auto Battler</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1084988" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Auto Battler" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5310" data-loc="Games Workshop" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5310" data-loc="Games Workshop" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Games Workshop</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5310" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Games Workshop" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5390" data-loc="Time Attack" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5390" data-loc="Time Attack" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Time Attack</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5390" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Time Attack" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1679" data-loc="Soccer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1679" data-loc="Soccer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Soccer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1679" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Soccer" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="11333" data-loc="Villain Protagonist" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="11333" data-loc="Villain Protagonist" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Villain Protagonist</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="11333" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Villain Protagonist" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7423" data-loc="Sniper" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7423" data-loc="Sniper" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Sniper</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7423" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Sniper" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6702" data-loc="Mars" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6702" data-loc="Mars" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mars</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6702" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mars" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7226" data-loc="Football" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7226" data-loc="Football" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Football</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7226" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Football" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="56690" data-loc="On-Rails Shooter" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="56690" data-loc="On-Rails Shooter" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">On-Rails Shooter</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="56690" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="On-Rails Shooter" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5382" data-loc="World War I" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5382" data-loc="World War I" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">World War I</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5382" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="World War I" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6041" data-loc="Horses" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6041" data-loc="Horses" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Horses</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6041" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Horses" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1674" data-loc="Typing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1674" data-loc="Typing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Typing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1674" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Typing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4137" data-loc="Transhumanism" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4137" data-loc="Transhumanism" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Transhumanism</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4137" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Transhumanism" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="150626" data-loc="Gaming" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="150626" data-loc="Gaming" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Gaming</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="150626" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Gaming" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7622" data-loc="Offroad" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7622" data-loc="Offroad" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Offroad</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7622" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Offroad" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="16250" data-loc="Gambling" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="16250" data-loc="Gambling" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Gambling</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="16250" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Gambling" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="17015" data-loc="Werewolves" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="17015" data-loc="Werewolves" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Werewolves</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="17015" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Werewolves" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="15954" data-loc="Silent Protagonist" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="15954" data-loc="Silent Protagonist" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Silent Protagonist</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="15954" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Silent Protagonist" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7113" data-loc="Crowdfunded" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7113" data-loc="Crowdfunded" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Crowdfunded</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7113" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Crowdfunded" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="776177" data-loc="360 Video" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="776177" data-loc="360 Video" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">360 Video</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="776177" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="360 Video" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1723" data-loc="Action RTS" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1723" data-loc="Action RTS" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Action RTS</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1723" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Action RTS" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4184" data-loc="Chess" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4184" data-loc="Chess" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Chess</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4184" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Chess" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="10437" data-loc="Trivia" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="10437" data-loc="Trivia" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Trivia</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="10437" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Trivia" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="12190" data-loc="Boxing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="12190" data-loc="Boxing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Boxing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="12190" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Boxing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="14720" data-loc="Nostalgia" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="14720" data-loc="Nostalgia" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Nostalgia</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="14720" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Nostalgia" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4520" data-loc="Farming" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4520" data-loc="Farming" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Farming</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4520" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Farming" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8075" data-loc="TrackIR" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8075" data-loc="TrackIR" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">TrackIR</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8075" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="TrackIR" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="454187" data-loc="Traditional Roguelike" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="454187" data-loc="Traditional Roguelike" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Traditional Roguelike</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="454187" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Traditional Roguelike" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1733" data-loc="Unforgiving" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1733" data-loc="Unforgiving" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Unforgiving</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1733" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Unforgiving" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1736" data-loc="LEGO" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1736" data-loc="LEGO" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">LEGO</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1736" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="LEGO" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6621" data-loc="Pinball" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6621" data-loc="Pinball" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Pinball</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6621" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Pinball" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1091588" data-loc="Roguelike Deckbuilder" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1091588" data-loc="Roguelike Deckbuilder" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Roguelike Deckbuilder</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1091588" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Roguelike Deckbuilder" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="92092" data-loc="Jet" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="92092" data-loc="Jet" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Jet</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="92092" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Jet" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="6948" data-loc="Rome" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="6948" data-loc="Rome" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Rome</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="6948" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Rome" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="198913" data-loc="Motorbike" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="198913" data-loc="Motorbike" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Motorbike</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="198913" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Motorbike" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7038" data-loc="Golf" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7038" data-loc="Golf" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Golf</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7038" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Golf" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="4291" data-loc="Spaceships" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="4291" data-loc="Spaceships" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Spaceships</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="4291" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Spaceships" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1100686" data-loc="Outbreak Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1100686" data-loc="Outbreak Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Outbreak Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1100686" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Outbreak Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="61357" data-loc="Electronic Music" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="61357" data-loc="Electronic Music" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Electronic Music</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="61357" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Electronic Music" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="3796" data-loc="Based On A Novel" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="3796" data-loc="Based On A Novel" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Based On A Novel</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="3796" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Based On A Novel" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="12286" data-loc="Warhammer 40K" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="12286" data-loc="Warhammer 40K" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Warhammer 40K</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="12286" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Warhammer 40K" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="29855" data-loc="Ambient" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="29855" data-loc="Ambient" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Ambient</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="29855" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Ambient" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="856791" data-loc="Asymmetric VR" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="856791" data-loc="Asymmetric VR" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Asymmetric VR</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="856791" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Asymmetric VR" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1100688" data-loc="Medical Sim" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1100688" data-loc="Medical Sim" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Medical Sim</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1100688" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Medical Sim" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="19780" data-loc="Submarine" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="19780" data-loc="Submarine" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Submarine</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="19780" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Submarine" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="123332" data-loc="Bikes" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="123332" data-loc="Bikes" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Bikes</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="123332" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Bikes" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="71389" data-loc="Spelling" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="71389" data-loc="Spelling" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Spelling</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="71389" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Spelling" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1746" data-loc="Basketball" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1746" data-loc="Basketball" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Basketball</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1746" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Basketball" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="922563" data-loc="Roguevania" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="922563" data-loc="Roguevania" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Roguevania</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="922563" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Roguevania" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="745697" data-loc="Social Deduction" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="745697" data-loc="Social Deduction" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Social Deduction</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="745697" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Social Deduction" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="769306" data-loc="Escape Room" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="769306" data-loc="Escape Room" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Escape Room</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="769306" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Escape Room" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="22955" data-loc="Mini Golf" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="22955" data-loc="Mini Golf" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mini Golf</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="22955" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Mini Golf" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="14906" data-loc="Intentionally Awkward Controls" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="14906" data-loc="Intentionally Awkward Controls" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Intentionally Awkward Controls</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="14906" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Intentionally Awkward Controls" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="189941" data-loc="Instrumental Music" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="189941" data-loc="Instrumental Music" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Instrumental Music</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="189941" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Instrumental Music" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="47827" data-loc="Wrestling" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="47827" data-loc="Wrestling" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Wrestling</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="47827" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Wrestling" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="17927" data-loc="Pool" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="17927" data-loc="Pool" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Pool</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="17927" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Pool" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5407" data-loc="Benchmark" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5407" data-loc="Benchmark" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Benchmark</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5407" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Benchmark" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="1753" data-loc="Skateboarding" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="1753" data-loc="Skateboarding" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Skateboarding</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="1753" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Skateboarding" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="348922" data-loc="Steam Machine" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="348922" data-loc="Steam Machine" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Steam Machine</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="348922" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Steam Machine" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="11634" data-loc="Vikings" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="11634" data-loc="Vikings" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Vikings</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="11634" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Vikings" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="17337" data-loc="Lemmings" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="17337" data-loc="Lemmings" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Lemmings</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="17337" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Lemmings" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5727" data-loc="Baseball" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5727" data-loc="Baseball" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Baseball</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5727" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Baseball" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5914" data-loc="Tennis" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5914" data-loc="Tennis" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tennis</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5914" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Tennis" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="603297" data-loc="Hardware" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="603297" data-loc="Hardware" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hardware</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="603297" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hardware" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="96359" data-loc="Skating" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="96359" data-loc="Skating" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Skating</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="96359" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Skating" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="324176" data-loc="Hockey" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="324176" data-loc="Hockey" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hockey</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="324176" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Hockey" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7328" data-loc="Bowling" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7328" data-loc="Bowling" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Bowling</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7328" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Bowling" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="19568" data-loc="Cycling" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="19568" data-loc="Cycling" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cycling</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="19568" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Cycling" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="143739" data-loc="Electronic" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="143739" data-loc="Electronic" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Electronic</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="143739" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Electronic" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="15868" data-loc="Motocross" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="15868" data-loc="Motocross" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Motocross</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="15868" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Motocross" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="233824" data-loc="Feature Film" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="233824" data-loc="Feature Film" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Feature Film</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="233824" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Feature Film" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="27758" data-loc="Voice Control" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="27758" data-loc="Voice Control" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Voice Control</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="27758" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Voice Control" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="337964" data-loc="Rock Music" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="337964" data-loc="Rock Music" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Rock Music</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="337964" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Rock Music" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="129761" data-loc="ATV" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="129761" data-loc="ATV" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">ATV</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="129761" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="ATV" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="8461" data-loc="Well-Written" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="8461" data-loc="Well-Written" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Well-Written</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="8461" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Well-Written" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="117648" data-loc="8-bit Music" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="117648" data-loc="8-bit Music" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">8-bit Music</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="117648" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="8-bit Music" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="252854" data-loc="BMX" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="252854" data-loc="BMX" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">BMX</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="252854" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="BMX" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="7309" data-loc="Skiing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="7309" data-loc="Skiing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Skiing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="7309" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Skiing" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="28444" data-loc="Snowboarding" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="28444" data-loc="Snowboarding" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Snowboarding</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="28444" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Snowboarding" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="tags" data-value="5941" data-loc="Reboot" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="tags" data-value="5941" data-loc="Reboot" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Reboot</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="untags" data-value="5941" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="Reboot" data-clientside="0" data-tooltip-text="Exclude results with this tag" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - <input type="hidden" id="tags" name="tags" value="" data-antifield='untags' /> </div> - <input type="text" id="TagSuggest"> - <script> - $J( '#TagFilter_Container' ).tableFilter({ maxvisible: 15, control: '#TagSuggest', dataattribute: 'loc', 'defaultText': 'search for more tags' }); - </script> - </div> - <a class="see_all_expander" href="#" onclick="ExpandOptions(this, 'TagFilter_Container'); return false;">See all</a> - </div> - - <input type="hidden" id="untags" name="untags" value="" data-antifield='tags' /> - <div class="block search_collapse_block" data-collapse-name="category1" data-collapse-default="true"> - <div class="block_header"><div>Show selected types</div></div> - <div class="block_content block_content_inner" id="narrow_category1" style="height: 150px;"> - <input type="hidden" id="category1" name="category1" value="" /> - <div class="tab_filter_control_row " data-param="category1" data-value="998" data-loc="Games" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="998" data-loc="Games" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Games</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="994" data-loc="Software" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="994" data-loc="Software" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Software</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="21" data-loc="Downloadable Content" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="21" data-loc="Downloadable Content" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Downloadable Content</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="10" data-loc="Demos" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="10" data-loc="Demos" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Demos</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="990" data-loc="Soundtracks" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="990" data-loc="Soundtracks" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Soundtracks</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="992" data-loc="Videos" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="992" data-loc="Videos" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Videos</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="997" data-loc="Mods" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="997" data-loc="Mods" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Mods</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="993" data-loc="Hardware" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="993" data-loc="Hardware" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hardware</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category1" data-value="996" data-loc="Include Bundles" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category1" data-value="996" data-loc="Include Bundles" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Include Bundles</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - </div> - <a class="see_all_expander nocollapse_only" href="#" onclick="ExpandOptions(this, 'narrow_category1'); return false;">See all</a> - </div> - <div class="block search_collapse_block" data-collapse-name="category3" data-collapse-default="true"> - <div class="block_header"><div>Narrow by number of players</div></div> - <div class="block_content block_content_inner"> - <input type="hidden" id="category3" name="category3" value="" /> - <div class="tab_filter_control_row " data-param="category3" data-value="2" data-loc="Single-player" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="2" data-loc="Single-player" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Single-player</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="1" data-loc="Multi-player" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="1" data-loc="Multi-player" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Multi-player</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="49" data-loc="PvP" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="49" data-loc="PvP" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">PvP</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="36" data-loc="Online PvP" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="36" data-loc="Online PvP" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Online PvP</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="47" data-loc="LAN PvP" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="47" data-loc="LAN PvP" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">LAN PvP</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="37" data-loc="Shared/Split Screen PvP" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="37" data-loc="Shared/Split Screen PvP" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Shared/Split Screen PvP</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="9" data-loc="Co-op" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="9" data-loc="Co-op" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Co-op</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="38" data-loc="Online Co-op" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="38" data-loc="Online Co-op" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Online Co-op</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="48" data-loc="LAN Co-op" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="48" data-loc="LAN Co-op" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">LAN Co-op</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="39" data-loc="Shared/Split Screen Co-op" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="39" data-loc="Shared/Split Screen Co-op" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Shared/Split Screen Co-op</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="24" data-loc="Shared/Split Screen" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="24" data-loc="Shared/Split Screen" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Shared/Split Screen</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category3" data-value="27" data-loc="Cross-Platform Multiplayer" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category3" data-value="27" data-loc="Cross-Platform Multiplayer" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Cross-Platform Multiplayer</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - </div> - </div> - <div class="block search_collapse_block" data-collapse-name="category2" data-collapse-default="true"> - <div class="block_header"><div>Narrow by feature</div></div> - <div class="block_content block_content_inner" id="narrow_category2" style="height: 150px;"> - <input type="hidden" id="special_categories" name="special_categories" value="" /> - <div class="tab_filter_control_row " data-param="special_categories" data-value="601" data-loc="Played with Steam Controller" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="special_categories" data-value="601" data-loc="Played with Steam Controller" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Played with Steam Controller</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - <input type="hidden" id="category2" name="category2" value="" /> - <div class="tab_filter_control_row " data-param="category2" data-value="50" data-loc="Additional High-Quality Audio" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="50" data-loc="Additional High-Quality Audio" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Additional High-Quality Audio</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="22" data-loc="Steam Achievements" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="22" data-loc="Steam Achievements" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Steam Achievements</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="28" data-loc="Full controller support" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="28" data-loc="Full controller support" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Full controller support</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="29" data-loc="Steam Trading Cards" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="29" data-loc="Steam Trading Cards" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Steam Trading Cards</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="13" data-loc="Captions available" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="13" data-loc="Captions available" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Captions available</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="51" data-loc="Steam Workshop" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="51" data-loc="Steam Workshop" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Steam Workshop</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="30" data-loc="Steam Workshop" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="30" data-loc="Steam Workshop" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Steam Workshop</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="40" data-loc="SteamVR Collectibles" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="40" data-loc="SteamVR Collectibles" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">SteamVR Collectibles</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="18" data-loc="Partial Controller Support" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="18" data-loc="Partial Controller Support" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Partial Controller Support</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="23" data-loc="Steam Cloud" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="23" data-loc="Steam Cloud" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Steam Cloud</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="8" data-loc="Valve Anti-Cheat enabled" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="8" data-loc="Valve Anti-Cheat enabled" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Valve Anti-Cheat enabled</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="16" data-loc="Includes Source SDK" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="16" data-loc="Includes Source SDK" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Includes Source SDK</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="41" data-loc="Remote Play on Phone" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="41" data-loc="Remote Play on Phone" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Remote Play on Phone</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="42" data-loc="Remote Play on Tablet" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="42" data-loc="Remote Play on Tablet" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Remote Play on Tablet</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="43" data-loc="Remote Play on TV" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="43" data-loc="Remote Play on TV" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Remote Play on TV</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="category2" data-value="44" data-loc="Remote Play Together" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="category2" data-value="44" data-loc="Remote Play Together" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Remote Play Together</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - </div> - <a class="see_all_expander nocollapse_only" href="#" onclick="ExpandOptions(this, 'narrow_category2'); return false;">See all</a> - </div> - <div class="block search_collapse_block" data-collapse-name="vrsupport" data-collapse-default="true"> - <div class="block_header"><div>Narrow by VR Support</div></div> - <div class="block_content block_content_inner" id="narrow_byVR" style="height: 168px;"> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="401" data-loc="VR Only" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="401" data-loc="VR Only" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">VR Only</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - <span class="tab_filter_control_not " data-param="unvrsupport" data-value="401" data-icon="https://store.cloudflare.steamstatic.com/public/images/search_crouton_not.svg" data-loc="VR Only" data-clientside="0" data-tooltip-text="Exclude VR Only games" ><img src="https://store.cloudflare.steamstatic.com/public/images/search_checkbox_not.svg" width="16px" height="16px"></span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="402" data-loc="VR Supported" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="402" data-loc="VR Supported" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">VR Supported</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - <div class="block_header"><div>Headsets</div></div> - <div class="tab_filter_control_row " data-param="vrsupport" data-value="105" data-loc="Valve Index" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="105" data-loc="Valve Index" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Valve Index</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="101" data-loc="HTC Vive" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="101" data-loc="HTC Vive" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">HTC Vive</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="102" data-loc="Oculus Rift" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="102" data-loc="Oculus Rift" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Oculus Rift</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="104" data-loc="Windows Mixed Reality" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="104" data-loc="Windows Mixed Reality" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Windows Mixed Reality</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - <div class="block_header"><div>Input</div></div> - <div class="tab_filter_control_row " data-param="vrsupport" data-value="201" data-loc="Tracked Motion Controllers" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="201" data-loc="Tracked Motion Controllers" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Tracked Motion Controllers</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="202" data-loc="Gamepad" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="202" data-loc="Gamepad" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Gamepad</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="203" data-loc="Keyboard / Mouse" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="203" data-loc="Keyboard / Mouse" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Keyboard / Mouse</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - <div class="block_header"><div>Play Area</div></div> - <div class="tab_filter_control_row " data-param="vrsupport" data-value="301" data-loc="Seated" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="301" data-loc="Seated" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Seated</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="302" data-loc="Standing" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="302" data-loc="Standing" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Standing</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="vrsupport" data-value="303" data-loc="Room-Scale" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="vrsupport" data-value="303" data-loc="Room-Scale" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Room-Scale</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - <input type="hidden" id="vrsupport" name="vrsupport" value="" data-antifield='unvrsupport' /> <input type="hidden" id="unvrsupport" name="unvrsupport" value="" data-antifield='vrsupport' /> </div> - <a class="see_all_expander nocollapse_only" href="#" onclick="ExpandOptions(this, 'narrow_byVR'); return false;">See all</a> - </div> - <div class="block search_collapse_block" data-collapse-name="os"> - <div class="block_header"><div>Narrow by OS</div></div> - <div class="block_content block_content_inner"> - <input type="hidden" id="os" name="os" value="" /> - <div class="tab_filter_control_row " data-param="os" data-value="win" data-loc="Windows" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="os" data-value="win" data-loc="Windows" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Windows</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="os" data-value="mac" data-loc="macOS" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="os" data-value="mac" data-loc="macOS" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">macOS</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="os" data-value="linux" data-loc="SteamOS + Linux" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="os" data-value="linux" data-loc="SteamOS + Linux" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">SteamOS + Linux</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - </div> - </div> - - - <div class="block"> - <div class="block_header"><div>Narrow by language</div></div> - <div id="narrow_language" class="block_content block_content_inner" style="height: 28px;"> - <input type="hidden" id="supportedlang" name="supportedlang" value="" /> - <div class="tab_filter_control_row " data-param="supportedlang" data-value="english" data-loc="English" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="english" data-loc="English" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">English</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="schinese" data-loc="Simplified Chinese" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="schinese" data-loc="Simplified Chinese" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Simplified Chinese</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="tchinese" data-loc="Traditional Chinese" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="tchinese" data-loc="Traditional Chinese" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Traditional Chinese</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="japanese" data-loc="Japanese" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="japanese" data-loc="Japanese" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Japanese</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="koreana" data-loc="Korean" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="koreana" data-loc="Korean" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Korean</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="thai" data-loc="Thai" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="thai" data-loc="Thai" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Thai</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="bulgarian" data-loc="Bulgarian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="bulgarian" data-loc="Bulgarian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Bulgarian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="czech" data-loc="Czech" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="czech" data-loc="Czech" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Czech</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="danish" data-loc="Danish" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="danish" data-loc="Danish" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Danish</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="german" data-loc="German" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="german" data-loc="German" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">German</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="spanish" data-loc="Spanish - Spain" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="spanish" data-loc="Spanish - Spain" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Spanish - Spain</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="latam" data-loc="Spanish - Latin America" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="latam" data-loc="Spanish - Latin America" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Spanish - Latin America</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="greek" data-loc="Greek" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="greek" data-loc="Greek" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Greek</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="french" data-loc="French" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="french" data-loc="French" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">French</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="italian" data-loc="Italian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="italian" data-loc="Italian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Italian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="hungarian" data-loc="Hungarian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="hungarian" data-loc="Hungarian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Hungarian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="dutch" data-loc="Dutch" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="dutch" data-loc="Dutch" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Dutch</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="norwegian" data-loc="Norwegian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="norwegian" data-loc="Norwegian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Norwegian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="polish" data-loc="Polish" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="polish" data-loc="Polish" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Polish</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="portuguese" data-loc="Portuguese" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="portuguese" data-loc="Portuguese" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Portuguese</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="brazilian" data-loc="Portuguese - Brazil" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="brazilian" data-loc="Portuguese - Brazil" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Portuguese - Brazil</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="romanian" data-loc="Romanian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="romanian" data-loc="Romanian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Romanian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="russian" data-loc="Russian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="russian" data-loc="Russian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Russian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="finnish" data-loc="Finnish" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="finnish" data-loc="Finnish" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Finnish</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="swedish" data-loc="Swedish" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="swedish" data-loc="Swedish" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Swedish</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="turkish" data-loc="Turkish" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="turkish" data-loc="Turkish" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Turkish</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="vietnamese" data-loc="Vietnamese" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="vietnamese" data-loc="Vietnamese" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Vietnamese</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - - <div class="tab_filter_control_row " data-param="supportedlang" data-value="ukrainian" data-loc="Ukrainian" data-clientside="0"> - <span class="tab_filter_control tab_filter_control_include " data-param="supportedlang" data-value="ukrainian" data-loc="Ukrainian" data-clientside="0"> - <span> - <span class="tab_filter_control_checkbox"></span> - <span class="tab_filter_control_label">Ukrainian</span> - <span class="tab_filter_control_count" style="display: none;"></span> - </span> - </span> - </div> - </div> - <a class="see_all_expander" href="#" onclick="ExpandOptions(this, 'narrow_language'); return false;">See all</a> - </div> - - </div> - <div style="clear: both;"></div> - </div> -</div> -<!-- End Main Background --> - -<div id="hidden_searchform_elements"> - <!-- in responsive mode, the hidden right column inputs are reparented here so they are part of the form --> - <input type="hidden" id="search_snr_value" name="snr" value="1_7_7_2300_7"> - <input type="hidden" name="list_of_subs" value=""> - - <input type="hidden" name="specials" id="specials" value="1"> - <input type="hidden" name="filter" value=""> - <input type="hidden" name="genre" value=""> - <input type="hidden" name="salepage" value=""> - <input type="hidden" name="publisher" id="publisher" value=""> - <input type="hidden" name="developer" id="developer" value=""> - <input type="hidden" name="manufacturer" id="manufacturer" value=""> - <input type="hidden" name="franchise" id="franchise" value=""> - <input type="hidden" id="search_current_page" name="page" value="1"> - </div> - -</form> - - </div> <!-- responsive_page_legacy_content --> - - <div id="footer_spacer" style="" class=""></div> -<div id="footer" class=""> -<div class="footer_content"> - - <div class="rule"></div> - <div id="footer_logo_steam"><img src="https://store.cloudflare.steamstatic.com/public/images/v6/logo_steam_footer.png" alt="Valve Software" border="0" /></div> - - <div id="footer_logo"><a href="http://www.valvesoftware.com" target="_blank" rel="noreferrer"><img src="https://store.cloudflare.steamstatic.com/public/images/footerLogo_valve_new.png" alt="Valve Software" border="0" /></a></div> - <div id="footer_text" data-panel="{"flow-children":"row"}" > - <div>© 2021 Valve Corporation. All rights reserved. All trademarks are property of their respective owners in the US and other countries.</div> - <div>VAT included in all prices where applicable. - - <a href="https://store.steampowered.com/privacy_agreement/?snr=1_44_44_" target="_blank" rel="noreferrer">Privacy Policy</a> - | - <a href="https://store.steampowered.com/legal/?snr=1_44_44_" target="_blank" rel="noreferrer">Legal</a> - | - <a href="https://store.steampowered.com/subscriber_agreement/?snr=1_44_44_" target="_blank" rel="noreferrer">Steam Subscriber Agreement</a> - | - <a href="https://store.steampowered.com/steam_refunds/?snr=1_44_44_" target="_blank" rel="noreferrer">Refunds</a> - | - <a href="https://store.steampowered.com/account/cookiepreferences/?snr=1_44_44_" target="_blank" rel="noreferrer">Cookies</a> - - </div> - <div class="responsive_optin_link"> - <div class="btn_medium btnv6_grey_black" onclick="Responsive_RequestMobileView()"> - <span>View mobile website</span> - </div> - </div> - - </div> - - - - <div style="clear: left;"></div> - <br> - - <div class="rule"></div> - - <div class="valve_links" data-panel="{"flow-children":"row"}" > - <a href="http://www.valvesoftware.com/about" target="_blank" rel="noreferrer">About Valve</a> - | <a href="http://www.valvesoftware.com" target="_blank" rel="noreferrer">Jobs</a> - | <a href="http://www.steampowered.com/steamworks/" target="_blank" rel="noreferrer">Steamworks</a> - | <a href="https://partner.steamgames.com/steamdirect" target="_blank" rel="noreferrer">Steam Distribution</a> - | <a href="https://help.steampowered.com/en/?snr=1_44_44_">Support</a> - | <a href="https://store.steampowered.com/digitalgiftcards/?snr=1_44_44_" target="_blank" rel="noreferrer">Gift Cards</a> - | <a href="https://steamcommunity.com/linkfilter/?url=http://www.facebook.com/Steam" target="_blank" rel="noopener"><img src="https://store.cloudflare.steamstatic.com/public/images/ico/ico_facebook.gif"> Steam</a> - | <a href="http://twitter.com/steam" target="_blank" rel="noreferrer"><img src="https://store.cloudflare.steamstatic.com/public/images/ico/ico_twitter.gif"> @steam</a> - </div> - -</div> -</div> - </div> <!-- responsive_page_content --> - -</div> <!-- responsive_page_frame --> -</body> -</html> \ No newline at end of file diff --git a/config/settings.json.example b/config/settings.json.example deleted file mode 100644 index b6d59fc..0000000 --- a/config/settings.json.example +++ /dev/null @@ -1,7 +0,0 @@ -{ - "token": "YOUR_DISCORD_TOKEN", - "prefix": "!", - "owner_id": "YOUR_USER_ID", - "owner_reporting": false, - "presence_refresh_interval": 900000 -} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index e78bc4d..0000000 --- a/jest.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - testEnvironment: 'node', - setupFiles: ['./test/setupEnv.js'] -}; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 879bac7..0000000 --- a/package-lock.json +++ /dev/null @@ -1,11227 +0,0 @@ -{ - "name": "discord-free-games-notifier", - "version": "2.0.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "discord-free-games-notifier", - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "@greencoast/discord.js-extended": "^1.2.0", - "@greencoast/logger": "^1.1.0", - "axios": "^0.21.1", - "cheerio": "^1.0.0-rc.10", - "cron": "^1.8.2", - "discord.js": "^12.5.3", - "level": "^7.0.0" - }, - "devDependencies": { - "@types/jest": "^27.0.1", - "eslint": "^7.32.0", - "eslint-config-greencoast": "0.0.2", - "jest": "^27.1.0", - "nodemon": "^2.0.12", - "text-encoding": "^0.7.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.4.tgz", - "integrity": "sha512-Lkcv9I4a8bgUI8LJOLM6IKv6hnz1KOju6KM1lceqVMKlKKqNRopYd2Pc9MgIurqvMJ6BooemrnJz8jlIiQIpsA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.4.tgz", - "integrity": "sha512-xmzz+7fRpjrvDUj+GV7zfz/R3gSK2cOxGlazaXooxspCr539cbTXJKvBJzSVI2pPhcRGquoOtaIkKCsHQUiO3w==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", - "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", - "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" - }, - "node_modules/@discordjs/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@greencoast/discord.js-extended": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@greencoast/discord.js-extended/-/discord.js-extended-1.2.0.tgz", - "integrity": "sha512-01LCt8k/xIOPO/9AMLHx9OYFhkNHBs+4t5uxyguZwrvdBbFCM1/oCV6IfieIbOu+elNBj1GxaHn2n/D2Qw7cFw==", - "dependencies": { - "@greencoast/logger": "^1.1.0", - "common-tags": "^1.8.0", - "dayjs": "^1.10.5", - "humanize-duration": "^3.27.0", - "require-all": "^3.0.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "discord.js": "~12", - "level": "~7" - } - }, - "node_modules/@greencoast/logger": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@greencoast/logger/-/logger-1.1.0.tgz", - "integrity": "sha512-d2/dI0B7SMs6Pm3T7xyIBthkvXWOLhjgYpSTCvZ+fNED+R0XoiwBpT4fxYAJuXyFXA7FtJYELsp9a1zwk+qANA==", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.1.0.tgz", - "integrity": "sha512-+Vl+xmLwAXLNlqT61gmHEixeRbS4L8MUzAjtpBCOPWH+izNI/dR16IeXjkXJdRtIVWVSf9DO1gdp67B1XorZhQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.1.0", - "jest-util": "^27.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.1.0.tgz", - "integrity": "sha512-3l9qmoknrlCFKfGdrmiQiPne+pUR4ALhKwFTYyOeKw6egfDwJkO21RJ1xf41rN8ZNFLg5W+w6+P4fUqq4EMRWA==", - "dev": true, - "dependencies": { - "@jest/console": "^27.1.0", - "@jest/reporters": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.1.0", - "jest-config": "^27.1.0", - "jest-haste-map": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.1.0", - "jest-resolve-dependencies": "^27.1.0", - "jest-runner": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "jest-watcher": "^27.1.0", - "micromatch": "^4.0.4", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.1.0.tgz", - "integrity": "sha512-wRp50aAMY2w1U2jP1G32d6FUVBNYqmk8WaGkiIEisU48qyDV0WPtw3IBLnl7orBeggveommAkuijY+RzVnNDOQ==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "jest-mock": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.1.0.tgz", - "integrity": "sha512-22Zyn8il8DzpS+30jJNVbTlm7vAtnfy1aYvNeOEHloMlGy1PCYLHa4PWlSws0hvNsMM5bON6GISjkLoQUV3oMA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "@sinonjs/fake-timers": "^7.0.2", - "@types/node": "*", - "jest-message-util": "^27.1.0", - "jest-mock": "^27.1.0", - "jest-util": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.1.0.tgz", - "integrity": "sha512-73vLV4aNHAlAgjk0/QcSIzzCZSqVIPbmFROJJv9D3QUR7BI4f517gVdJpSrCHxuRH3VZFhe0yGG/tmttlMll9g==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.1.0", - "@jest/types": "^27.1.0", - "expect": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.1.0.tgz", - "integrity": "sha512-5T/zlPkN2HnK3Sboeg64L5eC8iiaZueLpttdktWTJsvALEtP2YMkC5BQxwjRWQACG9SwDmz+XjjkoxXUDMDgdw==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.1.0", - "jest-resolve": "^27.1.0", - "jest-util": "^27.1.0", - "jest-worker": "^27.1.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/source-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", - "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.1.0.tgz", - "integrity": "sha512-Aoz00gpDL528ODLghat3QSy6UBTD5EmmpjrhZZMK/v1Q2/rRRqTGnFxHuEkrD4z/Py96ZdOHxIWkkCKRpmnE1A==", - "dev": true, - "dependencies": { - "@jest/console": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.1.0.tgz", - "integrity": "sha512-lnCWawDr6Z1DAAK9l25o3AjmKGgcutq1iIbp+hC10s/HxnB8ZkUsYq1FzjOoxxZ5hW+1+AthBtvS4x9yno3V1A==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.1.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-runtime": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.1.0.tgz", - "integrity": "sha512-ZRGCA2ZEVJ00ubrhkTG87kyLbN6n55g1Ilq0X9nJb5bX3MhMp3O6M7KG+LvYu+nZRqG5cXsQnJEdZbdpTAV8pQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.1.0", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-util": "^27.1.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/types": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.0.tgz", - "integrity": "sha512-pRP5cLIzN7I7Vp6mHKRSaZD7YpBTK7hawx5si8trMKqk4+WOdK8NEKOTO2G8PKWD1HbKMVckVB6/XHh/olhf2g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.15", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.15.tgz", - "integrity": "sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", - "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", - "dev": true, - "dependencies": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "node_modules/@types/node": { - "version": "16.7.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", - "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", - "dev": true - }, - "node_modules/@types/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/abstract-leveldown": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.0.0.tgz", - "integrity": "sha512-mFAi5sB/UjpNYglrQ4irzdmr2mbQtE94OJbrAYuK2yRARjH/OACinN1meOAorfnaLPMQdFymSQMlkiDm9AXXKQ==", - "dependencies": { - "buffer": "^6.0.3", - "is-buffer": "^2.0.5", - "level-concat-iterator": "^3.0.0", - "level-supports": "^2.0.0", - "queue-microtask": "^1.2.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, - "dependencies": { - "string-width": "^3.0.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/babel-jest": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.1.0.tgz", - "integrity": "sha512-6NrdqzaYemALGCuR97QkC/FkFIEBWP5pw5TMJoUHZTVXyOgocujp6A0JE2V6gE0HtqAAv6VKU/nI+OCR1Z4gHA==", - "dev": true, - "dependencies": { - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.0.6", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.6.tgz", - "integrity": "sha512-CewFeM9Vv2gM7Yr9n5eyyLVPRSiBnk6lKZRjgwYnGKSl9M14TMn2vkN02wTF04OGuSDLEzlWiMzvjXuW9mB6Gw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.0.6.tgz", - "integrity": "sha512-WObA0/Biw2LrVVwZkF/2GqbOdzhKD6Fkdwhoy9ASIrOWr/zodcSpQh72JOkEn6NWyjmnPDjNSqaGN4KnpKzhXw==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^27.0.6", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "node_modules/boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.8.tgz", - "integrity": "sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001251", - "colorette": "^1.3.0", - "electron-to-chromium": "^1.3.811", - "escalade": "^3.1.1", - "node-releases": "^1.1.75" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001252", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz", - "integrity": "sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/catering": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.0.0.tgz", - "integrity": "sha512-aD/WmxhGwUGsVPrj8C80vH7C7GphJilYVSdudoV4u16XdrLF7CVyfBmENsc4tLTVsJJzCRid8GbwJ7mcPLee6Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "dependencies": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", - "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", - "dependencies": { - "css-select": "^4.1.3", - "css-what": "^5.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0", - "domutils": "^2.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colorette": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", - "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/common-tags": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", - "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cron": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", - "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", - "dependencies": { - "moment-timezone": "^0.5.x" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/dayjs": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz", - "integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==" - }, - "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "node_modules/deferred-leveldown": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-6.0.0.tgz", - "integrity": "sha512-F6CLAZzNeURojlH4MCigZr54tNz+xDSi06YXsDr5uLSKeF3JKnvnQWTqd+RETh2hbWTJR3qDzGicQOWS5ZQ1BQ==", - "dependencies": { - "abstract-leveldown": "^7.0.0", - "inherits": "^2.0.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", - "deprecated": "no longer supported", - "dependencies": { - "@discordjs/collection": "^0.1.6", - "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.1", - "prism-media": "^1.2.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.3.830", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", - "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encoding-down": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-7.0.0.tgz", - "integrity": "sha512-hor6z2W/ZrVqDYMawQp7VtfEt6BrvYw+mgjWLauUMZsIBjMt1/k5aa+JreLbtjwJdkjrZ39TU+pV5GpHPGRpog==", - "dependencies": { - "abstract-leveldown": "^7.0.0", - "inherits": "^2.0.3", - "level-codec": "^10.0.0", - "level-errors": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", - "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-greencoast": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-greencoast/-/eslint-config-greencoast-0.0.2.tgz", - "integrity": "sha512-M8RcWRLZrwknxPUL9TP4w9HeJdkv1mVApTAp47yc35GoP+mL86HPqfia4o/BN3Bm4UcuoJyMRFOx61m7pOmKHQ==", - "dev": true, - "dependencies": { - "eslint-config-react-app": "^5.1.0" - }, - "peerDependencies": { - "eslint": ">= 4", - "eslint-plugin-jsx-a11y": ">= 6.2.3", - "eslint-plugin-react": ">= 7.17.0" - } - }, - "node_modules/eslint-config-react-app": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz", - "integrity": "sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ==", - "dev": true, - "dependencies": { - "confusing-browser-globals": "^1.0.9" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "2.x", - "@typescript-eslint/parser": "2.x", - "babel-eslint": "10.x", - "eslint": "6.x", - "eslint-plugin-flowtype": "3.x || 4.x", - "eslint-plugin-import": "2.x", - "eslint-plugin-jsx-a11y": "6.x", - "eslint-plugin-react": "7.x", - "eslint-plugin-react-hooks": "1.x || 2.x" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.1.0.tgz", - "integrity": "sha512-9kJngV5hOJgkFil4F/uXm3hVBubUK2nERVfvqNNwxxuW8ZOUwSTTSysgfzckYtv/LBzj/LJXbiAF7okHCXgdug==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-regex-util": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", - "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "dev": true, - "dependencies": { - "ini": "1.3.7" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/humanize-duration": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz", - "integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, - "node_modules/is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.1.1" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.1.0.tgz", - "integrity": "sha512-pSQDVwRSwb109Ss13lcMtdfS9r8/w2Zz8+mTUA9VORD66GflCdl8nUFCqM96geOD2EBwWCNURrNAfQsLIDNBdg==", - "dev": true, - "dependencies": { - "@jest/core": "^27.1.0", - "import-local": "^3.0.2", - "jest-cli": "^27.1.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.1.0.tgz", - "integrity": "sha512-eRcb13TfQw0xiV2E98EmiEgs9a5uaBIqJChyl0G7jR9fCIvGjXovnDS6Zbku3joij4tXYcSK4SE1AXqOlUxjWg==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.1.0.tgz", - "integrity": "sha512-6FWtHs3nZyZlMBhRf1wvAC5CirnflbGJAY1xssSAnERLiiXQRH+wY2ptBVtXjX4gz4AA2EwRV57b038LmifRbA==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.1.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.1.0", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "pretty-format": "^27.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.1.0.tgz", - "integrity": "sha512-GMo7f76vMYUA3b3xOdlcKeKQhKcBIgurjERO2hojo0eLkKPGcw7fyIoanH+m6KOP2bLad+fGnF8aWOJYxzNPeg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.1.0", - "@jest/types": "^27.1.0", - "babel-jest": "^27.1.0", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "jest-circus": "^27.1.0", - "jest-environment-jsdom": "^27.1.0", - "jest-environment-node": "^27.1.0", - "jest-get-type": "^27.0.6", - "jest-jasmine2": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.1.0", - "jest-runner": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "micromatch": "^4.0.4", - "pretty-format": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.1.0.tgz", - "integrity": "sha512-rjfopEYl58g/SZTsQFmspBODvMSytL16I+cirnScWTLkQVXYVZfxm78DFfdIIXc05RCYuGjxJqrdyG4PIFzcJg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", - "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.1.0.tgz", - "integrity": "sha512-K/cNvQlmDqQMRHF8CaQ0XPzCfjP5HMJc2bIJglrIqI9fjwpNqITle63IWE+wq4p+3v+iBgh7Wq0IdGpLx5xjDg==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "jest-util": "^27.1.0", - "pretty-format": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.1.0.tgz", - "integrity": "sha512-JbwOcOxh/HOtsj56ljeXQCUJr3ivnaIlM45F5NBezFLVYdT91N5UofB1ux2B1CATsQiudcHdgTaeuqGXJqjJYQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.1.0", - "@jest/fake-timers": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "jest-mock": "^27.1.0", - "jest-util": "^27.1.0", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.1.0.tgz", - "integrity": "sha512-JIyJ8H3wVyM4YCXp7njbjs0dIT87yhGlrXCXhDKNIg1OjurXr6X38yocnnbXvvNyqVTqSI4M9l+YfPKueqL1lw==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.1.0", - "@jest/fake-timers": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "jest-mock": "^27.1.0", - "jest-util": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.1.0.tgz", - "integrity": "sha512-7mz6LopSe+eA6cTFMf10OfLLqRoIPvmMyz5/OnSXnHO7hB0aDP1iIeLWCXzAcYU5eIJVpHr12Bk9yyq2fTW9vg==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.1.0", - "jest-worker": "^27.1.0", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.1.0.tgz", - "integrity": "sha512-Z/NIt0wBDg3przOW2FCWtYjMn3Ip68t0SL60agD/e67jlhTyV3PIF8IzT9ecwqFbeuUSO2OT8WeJgHcalDGFzQ==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.1.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.1.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.1.0", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "pretty-format": "^27.1.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.1.0.tgz", - "integrity": "sha512-oHvSkz1E80VyeTKBvZNnw576qU+cVqRXUD3/wKXh1zpaki47Qty2xeHg2HKie9Hqcd2l4XwircgNOWb/NiGqdA==", - "dev": true, - "dependencies": { - "jest-get-type": "^27.0.6", - "pretty-format": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.1.0.tgz", - "integrity": "sha512-VmAudus2P6Yt/JVBRdTPFhUzlIN8DYJd+et5Rd9QDsO/Z82Z4iwGjo43U8Z+PTiz8CBvKvlb6Fh3oKy39hykkQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.1.0", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.1.0.tgz", - "integrity": "sha512-Eck8NFnJ5Sg36R9XguD65cf2D5+McC+NF5GIdEninoabcuoOfWrID5qJhufq5FB0DRKoiyxB61hS7MKoMD0trQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.1.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/jest-mock": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.1.0.tgz", - "integrity": "sha512-iT3/Yhu7DwAg/0HvvLCqLvrTKTRMyJlrrfJYWzuLSf9RCAxBoIXN3HoymZxMnYsC3eD8ewGbUa9jUknwBenx2w==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", - "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.1.0.tgz", - "integrity": "sha512-TXvzrLyPg0vLOwcWX38ZGYeEztSEmW+cQQKqc4HKDUwun31wsBXwotRlUz4/AYU/Fq4GhbMd/ileIWZEtcdmIA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "escalade": "^3.1.1", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "resolve": "^1.20.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.1.0.tgz", - "integrity": "sha512-Kq5XuDAELuBnrERrjFYEzu/A+i2W7l9HnPWqZEeKGEQ7m1R+6ndMbdXCVCx29Se1qwLZLgvoXwinB3SPIaitMQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-snapshot": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.1.0.tgz", - "integrity": "sha512-ZWPKr9M5w5gDplz1KsJ6iRmQaDT/yyAFLf18fKbb/+BLWsR1sCNC2wMT0H7pP3gDcBz0qZ6aJraSYUNAGSJGaw==", - "dev": true, - "dependencies": { - "@jest/console": "^27.1.0", - "@jest/environment": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.0.6", - "jest-environment-jsdom": "^27.1.0", - "jest-environment-node": "^27.1.0", - "jest-haste-map": "^27.1.0", - "jest-leak-detector": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-resolve": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-util": "^27.1.0", - "jest-worker": "^27.1.0", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.1.0.tgz", - "integrity": "sha512-okiR2cpGjY0RkWmUGGado6ETpFOi9oG3yV0CioYdoktkVxy5Hv0WRLWnJFuArSYS8cHMCNcceUUMGiIfgxCO9A==", - "dev": true, - "dependencies": { - "@jest/console": "^27.1.0", - "@jest/environment": "^27.1.0", - "@jest/fake-timers": "^27.1.0", - "@jest/globals": "^27.1.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-mock": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-serializer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", - "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.1.0.tgz", - "integrity": "sha512-eaeUBoEjuuRwmiRI51oTldUsKOohB1F6fPqWKKILuDi/CStxzp2IWekVUXbuHHoz5ik33ioJhshiHpgPFbYgcA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.1.0", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.1.0", - "jest-get-type": "^27.0.6", - "jest-haste-map": "^27.1.0", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-resolve": "^27.1.0", - "jest-util": "^27.1.0", - "natural-compare": "^1.4.0", - "pretty-format": "^27.1.0", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-util": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.1.0.tgz", - "integrity": "sha512-edSLD2OneYDKC6gZM1yc+wY/877s/fuJNoM1k3sOEpzFyeptSmke3SLnk1dDHk9CgTA+58mnfx3ew3J11Kes/w==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.1.0.tgz", - "integrity": "sha512-QiJ+4XuSuMsfPi9zvdO//IrSRSlG6ybJhOpuqYSsuuaABaNT84h0IoD6vvQhThBOKT+DIKvl5sTM0l6is9+SRA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.1.0.tgz", - "integrity": "sha512-ivaWTrA46aHWdgPDgPypSHiNQjyKnLBpUIHeBaGg11U+pDzZpkffGlcB1l1a014phmG0mHgkOHtOgiqJQM6yKQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.1.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-worker": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.1.0.tgz", - "integrity": "sha512-mO4PHb2QWLn9yRXGp7rkvXLAYuxwhq1ZYUo0LoDhg8wqvv4QizP1ZWEJOeolgbEgAWZLIEU0wsku8J+lGWfBhg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jest/node_modules/jest-cli": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.1.0.tgz", - "integrity": "sha512-h6zPUOUu+6oLDrXz0yOWY2YXvBLk8gQinx4HbZ7SF4V3HzasQf+ncoIbKENUMwXyf54/6dBkYXvXJos+gOHYZw==", - "dev": true, - "dependencies": { - "@jest/core": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "prompts": "^2.0.1", - "yargs": "^16.0.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/level": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-7.0.0.tgz", - "integrity": "sha512-QrBnjcWywalh86ms9hfizvxT5aBHrgWEu6rLChS9tFE2wwFU3aI1r0v+2SSZIyeUr4O4PFo8+sCc1kebahdhlw==", - "dependencies": { - "level-js": "^6.0.0", - "level-packager": "^6.0.0", - "leveldown": "^6.0.0" - }, - "engines": { - "node": ">=10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/level" - } - }, - "node_modules/level-codec": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-10.0.0.tgz", - "integrity": "sha512-QW3VteVNAp6c/LuV6nDjg7XDXx9XHK4abmQarxZmlRSDyXYk20UdaJTSX6yzVvQ4i0JyWSB7jert0DsyD/kk6g==", - "dependencies": { - "buffer": "^6.0.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-concat-iterator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.0.0.tgz", - "integrity": "sha512-UHGiIdj+uiFQorOrURRvJF3Ei0uHc89ciM/aRi0qsWDV2f0HXypeXUPhJKL6DsONgSR76Pc0AI4sKYEYYRn2Dg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/level-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-3.0.0.tgz", - "integrity": "sha512-MZXOQT061uEjxxxq4C/Jf+M3RdEKK9e3NbxlN7yOp1LDYoLVAhE2i1j0b7XqXfl8FjFtUL7phwr3Sn0wXXoMqA==", - "dependencies": { - "errno": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-iterator-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-5.0.0.tgz", - "integrity": "sha512-wnb1+o+CVFUDdiSMR/ZymE2prPs3cjVLlXuDeSq9Zb8o032XrabGEXcTCsBxprAtseO3qvFeGzh6406z9sOTRA==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-js": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/level-js/-/level-js-6.0.0.tgz", - "integrity": "sha512-7dp7JuaoQoqKW4ZGvrV1RB5f51/ktLdEo9fSDsh3Ofmg7sKCMu3X0CIngbY/IUz/YyskhN7LRvEVIkZHCY3LKQ==", - "dependencies": { - "abstract-leveldown": "^7.0.0", - "buffer": "^6.0.3", - "inherits": "^2.0.3", - "ltgt": "^2.1.2" - } - }, - "node_modules/level-packager": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-6.0.0.tgz", - "integrity": "sha512-me656XRWfOVqs9wc+mWckZ6Rb1GuP33ndN4ZntDXwXFspX8cGA++Y+YqJsdE/mjTiipTuxXf047Z4rV62nOVuw==", - "dependencies": { - "encoding-down": "^7.0.0", - "levelup": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-supports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.0.0.tgz", - "integrity": "sha512-8UJgzo1pvWP1wq80ZlkL19fPeK7tlyy0sBY90+2pj0x/kvzHCoLDWyuFJJMrsTn33oc7hbMkS3SkjCxMRPHWaw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/leveldown": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.0.1.tgz", - "integrity": "sha512-D1fz5G58UrLBg5pXcN1pNxWtNLXwBiZsYt06XqjhD5zBHj/ws7xvZe3K0ZAhCbYuL9b4nbRCim8Z15HYWL2exQ==", - "hasInstallScript": true, - "dependencies": { - "abstract-leveldown": "^7.0.0", - "napi-macros": "~2.0.0", - "node-gyp-build": "~4.2.1" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/levelup": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-5.0.1.tgz", - "integrity": "sha512-MJvQgBRQmB+E5+d6Qbxqm05N4U9NzOxGNhXx0rR8maRBwmVuVV+m4IV3N4HzZJW8JwiJ0jj92RZaytcD+Hr1CA==", - "dependencies": { - "catering": "^2.0.0", - "deferred-leveldown": "^6.0.0", - "level-errors": "^3.0.0", - "level-iterator-stream": "^5.0.0", - "level-supports": "^2.0.0", - "queue-microtask": "^1.2.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "dependencies": { - "tmpl": "1.0.x" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dependencies": { - "mime-db": "1.49.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.33", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", - "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", - "dependencies": { - "moment": ">= 2.9.0" - }, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true - }, - "node_modules/napi-macros": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-releases": { - "version": "1.1.75", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", - "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", - "dev": true - }, - "node_modules/nodemon": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", - "integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pretty-format": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.1.0.tgz", - "integrity": "sha512-4aGaud3w3rxAO6OXmK3fwBFQ0bctIOG3/if+jYEFGNGIs0EvuidQm3bZ9mlP2/t9epLNC/12czabfy7TZNSwVA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prism-media": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.2.tgz", - "integrity": "sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g==", - "peerDependencies": { - "@discordjs/opus": "^0.5.0", - "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", - "node-opus": "^0.3.3", - "opusscript": "^0.0.8" - }, - "peerDependenciesMeta": { - "@discordjs/opus": { - "optional": true - }, - "ffmpeg-static": { - "optional": true - }, - "node-opus": { - "optional": true - }, - "opusscript": { - "optional": true - } - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "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/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/require-all": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", - "integrity": "sha1-Rz1JcEvjEBFc4ST3c4Ox69hnExI=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-encoding": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", - "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", - "deprecated": "no longer maintained", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "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/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "dependencies": { - "debug": "^2.2.0" - } - }, - "node_modules/undefsafe/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/undefsafe/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dev": true, - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/update-notifier/node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", - "integrity": "sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "dependencies": { - "makeerror": "1.0.x" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz", - "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "dev": true - }, - "@babel/core": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.4.tgz", - "integrity": "sha512-Lkcv9I4a8bgUI8LJOLM6IKv6hnz1KOju6KM1lceqVMKlKKqNRopYd2Pc9MgIurqvMJ6BooemrnJz8jlIiQIpsA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - }, - "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "dev": true, - "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "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=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.4.tgz", - "integrity": "sha512-xmzz+7fRpjrvDUj+GV7zfz/R3gSK2cOxGlazaXooxspCr539cbTXJKvBJzSVI2pPhcRGquoOtaIkKCsHQUiO3w==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", - "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - } - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", - "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@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==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - } - }, - "@greencoast/discord.js-extended": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@greencoast/discord.js-extended/-/discord.js-extended-1.2.0.tgz", - "integrity": "sha512-01LCt8k/xIOPO/9AMLHx9OYFhkNHBs+4t5uxyguZwrvdBbFCM1/oCV6IfieIbOu+elNBj1GxaHn2n/D2Qw7cFw==", - "requires": { - "@greencoast/logger": "^1.1.0", - "common-tags": "^1.8.0", - "dayjs": "^1.10.5", - "humanize-duration": "^3.27.0", - "require-all": "^3.0.0" - } - }, - "@greencoast/logger": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@greencoast/logger/-/logger-1.1.0.tgz", - "integrity": "sha512-d2/dI0B7SMs6Pm3T7xyIBthkvXWOLhjgYpSTCvZ+fNED+R0XoiwBpT4fxYAJuXyFXA7FtJYELsp9a1zwk+qANA==", - "requires": { - "chalk": "^4.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.1.0.tgz", - "integrity": "sha512-+Vl+xmLwAXLNlqT61gmHEixeRbS4L8MUzAjtpBCOPWH+izNI/dR16IeXjkXJdRtIVWVSf9DO1gdp67B1XorZhQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.1.0", - "jest-util": "^27.1.0", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.1.0.tgz", - "integrity": "sha512-3l9qmoknrlCFKfGdrmiQiPne+pUR4ALhKwFTYyOeKw6egfDwJkO21RJ1xf41rN8ZNFLg5W+w6+P4fUqq4EMRWA==", - "dev": true, - "requires": { - "@jest/console": "^27.1.0", - "@jest/reporters": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.1.0", - "jest-config": "^27.1.0", - "jest-haste-map": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.1.0", - "jest-resolve-dependencies": "^27.1.0", - "jest-runner": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "jest-watcher": "^27.1.0", - "micromatch": "^4.0.4", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.1.0.tgz", - "integrity": "sha512-wRp50aAMY2w1U2jP1G32d6FUVBNYqmk8WaGkiIEisU48qyDV0WPtw3IBLnl7orBeggveommAkuijY+RzVnNDOQ==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "jest-mock": "^27.1.0" - } - }, - "@jest/fake-timers": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.1.0.tgz", - "integrity": "sha512-22Zyn8il8DzpS+30jJNVbTlm7vAtnfy1aYvNeOEHloMlGy1PCYLHa4PWlSws0hvNsMM5bON6GISjkLoQUV3oMA==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "@sinonjs/fake-timers": "^7.0.2", - "@types/node": "*", - "jest-message-util": "^27.1.0", - "jest-mock": "^27.1.0", - "jest-util": "^27.1.0" - } - }, - "@jest/globals": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.1.0.tgz", - "integrity": "sha512-73vLV4aNHAlAgjk0/QcSIzzCZSqVIPbmFROJJv9D3QUR7BI4f517gVdJpSrCHxuRH3VZFhe0yGG/tmttlMll9g==", - "dev": true, - "requires": { - "@jest/environment": "^27.1.0", - "@jest/types": "^27.1.0", - "expect": "^27.1.0" - } - }, - "@jest/reporters": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.1.0.tgz", - "integrity": "sha512-5T/zlPkN2HnK3Sboeg64L5eC8iiaZueLpttdktWTJsvALEtP2YMkC5BQxwjRWQACG9SwDmz+XjjkoxXUDMDgdw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.1.0", - "jest-resolve": "^27.1.0", - "jest-util": "^27.1.0", - "jest-worker": "^27.1.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.0.0" - } - }, - "@jest/source-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", - "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.1.0.tgz", - "integrity": "sha512-Aoz00gpDL528ODLghat3QSy6UBTD5EmmpjrhZZMK/v1Q2/rRRqTGnFxHuEkrD4z/Py96ZdOHxIWkkCKRpmnE1A==", - "dev": true, - "requires": { - "@jest/console": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.1.0.tgz", - "integrity": "sha512-lnCWawDr6Z1DAAK9l25o3AjmKGgcutq1iIbp+hC10s/HxnB8ZkUsYq1FzjOoxxZ5hW+1+AthBtvS4x9yno3V1A==", - "dev": true, - "requires": { - "@jest/test-result": "^27.1.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-runtime": "^27.1.0" - } - }, - "@jest/transform": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.1.0.tgz", - "integrity": "sha512-ZRGCA2ZEVJ00ubrhkTG87kyLbN6n55g1Ilq0X9nJb5bX3MhMp3O6M7KG+LvYu+nZRqG5cXsQnJEdZbdpTAV8pQ==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.1.0", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-util": "^27.1.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - } - }, - "@jest/types": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.0.tgz", - "integrity": "sha512-pRP5cLIzN7I7Vp6mHKRSaZD7YpBTK7hawx5si8trMKqk4+WOdK8NEKOTO2G8PKWD1HbKMVckVB6/XHh/olhf2g==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.15", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.15.tgz", - "integrity": "sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", - "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", - "dev": true, - "requires": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "@types/node": { - "version": "16.7.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", - "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", - "dev": true - }, - "@types/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "abstract-leveldown": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.0.0.tgz", - "integrity": "sha512-mFAi5sB/UjpNYglrQ4irzdmr2mbQtE94OJbrAYuK2yRARjH/OACinN1meOAorfnaLPMQdFymSQMlkiDm9AXXKQ==", - "requires": { - "buffer": "^6.0.3", - "is-buffer": "^2.0.5", - "level-concat-iterator": "^3.0.0", - "level-supports": "^2.0.0", - "queue-microtask": "^1.2.3" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, - "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "babel-jest": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.1.0.tgz", - "integrity": "sha512-6NrdqzaYemALGCuR97QkC/FkFIEBWP5pw5TMJoUHZTVXyOgocujp6A0JE2V6gE0HtqAAv6VKU/nI+OCR1Z4gHA==", - "dev": true, - "requires": { - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.0.6", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.6.tgz", - "integrity": "sha512-CewFeM9Vv2gM7Yr9n5eyyLVPRSiBnk6lKZRjgwYnGKSl9M14TMn2vkN02wTF04OGuSDLEzlWiMzvjXuW9mB6Gw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.0.6.tgz", - "integrity": "sha512-WObA0/Biw2LrVVwZkF/2GqbOdzhKD6Fkdwhoy9ASIrOWr/zodcSpQh72JOkEn6NWyjmnPDjNSqaGN4KnpKzhXw==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^27.0.6", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.8.tgz", - "integrity": "sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001251", - "colorette": "^1.3.0", - "electron-to-chromium": "^1.3.811", - "escalade": "^3.1.1", - "node-releases": "^1.1.75" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001252", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz", - "integrity": "sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==", - "dev": true - }, - "catering": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.0.0.tgz", - "integrity": "sha512-aD/WmxhGwUGsVPrj8C80vH7C7GphJilYVSdudoV4u16XdrLF7CVyfBmENsc4tLTVsJJzCRid8GbwJ7mcPLee6Q==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "requires": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - } - }, - "cheerio-select": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", - "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", - "requires": { - "css-select": "^4.1.3", - "css-what": "^5.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0", - "domutils": "^2.7.0" - } - }, - "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "colorette": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", - "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "common-tags": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "confusing-browser-globals": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", - "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cron": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", - "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", - "requires": { - "moment-timezone": "^0.5.x" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, - "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - } - }, - "css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==" - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "dayjs": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz", - "integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==" - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "deferred-leveldown": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-6.0.0.tgz", - "integrity": "sha512-F6CLAZzNeURojlH4MCigZr54tNz+xDSi06YXsDr5uLSKeF3JKnvnQWTqd+RETh2hbWTJR3qDzGicQOWS5ZQ1BQ==", - "requires": { - "abstract-leveldown": "^7.0.0", - "inherits": "^2.0.3" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", - "dev": true - }, - "discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", - "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.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.830", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", - "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", - "dev": true - }, - "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encoding-down": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-7.0.0.tgz", - "integrity": "sha512-hor6z2W/ZrVqDYMawQp7VtfEt6BrvYw+mgjWLauUMZsIBjMt1/k5aa+JreLbtjwJdkjrZ39TU+pV5GpHPGRpog==", - "requires": { - "abstract-leveldown": "^7.0.0", - "inherits": "^2.0.3", - "level-codec": "^10.0.0", - "level-errors": "^3.0.0" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" - }, - "errno": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", - "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", - "requires": { - "prr": "~1.0.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - } - }, - "eslint-config-greencoast": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-greencoast/-/eslint-config-greencoast-0.0.2.tgz", - "integrity": "sha512-M8RcWRLZrwknxPUL9TP4w9HeJdkv1mVApTAp47yc35GoP+mL86HPqfia4o/BN3Bm4UcuoJyMRFOx61m7pOmKHQ==", - "dev": true, - "requires": { - "eslint-config-react-app": "^5.1.0" - } - }, - "eslint-config-react-app": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz", - "integrity": "sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ==", - "dev": true, - "requires": { - "confusing-browser-globals": "^1.0.9" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expect": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.1.0.tgz", - "integrity": "sha512-9kJngV5hOJgkFil4F/uXm3hVBubUK2nERVfvqNNwxxuW8ZOUwSTTSysgfzckYtv/LBzj/LJXbiAF7okHCXgdug==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-regex-util": "^27.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "follow-redirects": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", - "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "dev": true, - "requires": { - "ini": "1.3.7" - } - }, - "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "humanize-duration": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz", - "integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" - }, - "is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", - "dev": true, - "requires": { - "ci-info": "^3.1.1" - } - }, - "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - } - }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.1.0.tgz", - "integrity": "sha512-pSQDVwRSwb109Ss13lcMtdfS9r8/w2Zz8+mTUA9VORD66GflCdl8nUFCqM96geOD2EBwWCNURrNAfQsLIDNBdg==", - "dev": true, - "requires": { - "@jest/core": "^27.1.0", - "import-local": "^3.0.2", - "jest-cli": "^27.1.0" - }, - "dependencies": { - "jest-cli": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.1.0.tgz", - "integrity": "sha512-h6zPUOUu+6oLDrXz0yOWY2YXvBLk8gQinx4HbZ7SF4V3HzasQf+ncoIbKENUMwXyf54/6dBkYXvXJos+gOHYZw==", - "dev": true, - "requires": { - "@jest/core": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "prompts": "^2.0.1", - "yargs": "^16.0.3" - } - } - } - }, - "jest-changed-files": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.1.0.tgz", - "integrity": "sha512-eRcb13TfQw0xiV2E98EmiEgs9a5uaBIqJChyl0G7jR9fCIvGjXovnDS6Zbku3joij4tXYcSK4SE1AXqOlUxjWg==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "execa": "^5.0.0", - "throat": "^6.0.1" - } - }, - "jest-circus": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.1.0.tgz", - "integrity": "sha512-6FWtHs3nZyZlMBhRf1wvAC5CirnflbGJAY1xssSAnERLiiXQRH+wY2ptBVtXjX4gz4AA2EwRV57b038LmifRbA==", - "dev": true, - "requires": { - "@jest/environment": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.1.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.1.0", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "pretty-format": "^27.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - } - }, - "jest-config": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.1.0.tgz", - "integrity": "sha512-GMo7f76vMYUA3b3xOdlcKeKQhKcBIgurjERO2hojo0eLkKPGcw7fyIoanH+m6KOP2bLad+fGnF8aWOJYxzNPeg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.1.0", - "@jest/types": "^27.1.0", - "babel-jest": "^27.1.0", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "jest-circus": "^27.1.0", - "jest-environment-jsdom": "^27.1.0", - "jest-environment-node": "^27.1.0", - "jest-get-type": "^27.0.6", - "jest-jasmine2": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.1.0", - "jest-runner": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "micromatch": "^4.0.4", - "pretty-format": "^27.1.0" - } - }, - "jest-diff": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.1.0.tgz", - "integrity": "sha512-rjfopEYl58g/SZTsQFmspBODvMSytL16I+cirnScWTLkQVXYVZfxm78DFfdIIXc05RCYuGjxJqrdyG4PIFzcJg==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.1.0" - } - }, - "jest-docblock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", - "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.1.0.tgz", - "integrity": "sha512-K/cNvQlmDqQMRHF8CaQ0XPzCfjP5HMJc2bIJglrIqI9fjwpNqITle63IWE+wq4p+3v+iBgh7Wq0IdGpLx5xjDg==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "jest-util": "^27.1.0", - "pretty-format": "^27.1.0" - } - }, - "jest-environment-jsdom": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.1.0.tgz", - "integrity": "sha512-JbwOcOxh/HOtsj56ljeXQCUJr3ivnaIlM45F5NBezFLVYdT91N5UofB1ux2B1CATsQiudcHdgTaeuqGXJqjJYQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.1.0", - "@jest/fake-timers": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "jest-mock": "^27.1.0", - "jest-util": "^27.1.0", - "jsdom": "^16.6.0" - } - }, - "jest-environment-node": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.1.0.tgz", - "integrity": "sha512-JIyJ8H3wVyM4YCXp7njbjs0dIT87yhGlrXCXhDKNIg1OjurXr6X38yocnnbXvvNyqVTqSI4M9l+YfPKueqL1lw==", - "dev": true, - "requires": { - "@jest/environment": "^27.1.0", - "@jest/fake-timers": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "jest-mock": "^27.1.0", - "jest-util": "^27.1.0" - } - }, - "jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true - }, - "jest-haste-map": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.1.0.tgz", - "integrity": "sha512-7mz6LopSe+eA6cTFMf10OfLLqRoIPvmMyz5/OnSXnHO7hB0aDP1iIeLWCXzAcYU5eIJVpHr12Bk9yyq2fTW9vg==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.1.0", - "jest-worker": "^27.1.0", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-jasmine2": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.1.0.tgz", - "integrity": "sha512-Z/NIt0wBDg3przOW2FCWtYjMn3Ip68t0SL60agD/e67jlhTyV3PIF8IzT9ecwqFbeuUSO2OT8WeJgHcalDGFzQ==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.1.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.1.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.1.0", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "pretty-format": "^27.1.0", - "throat": "^6.0.1" - } - }, - "jest-leak-detector": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.1.0.tgz", - "integrity": "sha512-oHvSkz1E80VyeTKBvZNnw576qU+cVqRXUD3/wKXh1zpaki47Qty2xeHg2HKie9Hqcd2l4XwircgNOWb/NiGqdA==", - "dev": true, - "requires": { - "jest-get-type": "^27.0.6", - "pretty-format": "^27.1.0" - } - }, - "jest-matcher-utils": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.1.0.tgz", - "integrity": "sha512-VmAudus2P6Yt/JVBRdTPFhUzlIN8DYJd+et5Rd9QDsO/Z82Z4iwGjo43U8Z+PTiz8CBvKvlb6Fh3oKy39hykkQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.1.0", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.1.0" - } - }, - "jest-message-util": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.1.0.tgz", - "integrity": "sha512-Eck8NFnJ5Sg36R9XguD65cf2D5+McC+NF5GIdEninoabcuoOfWrID5qJhufq5FB0DRKoiyxB61hS7MKoMD0trQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.1.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - } - } - }, - "jest-mock": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.1.0.tgz", - "integrity": "sha512-iT3/Yhu7DwAg/0HvvLCqLvrTKTRMyJlrrfJYWzuLSf9RCAxBoIXN3HoymZxMnYsC3eD8ewGbUa9jUknwBenx2w==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", - "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", - "dev": true - }, - "jest-resolve": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.1.0.tgz", - "integrity": "sha512-TXvzrLyPg0vLOwcWX38ZGYeEztSEmW+cQQKqc4HKDUwun31wsBXwotRlUz4/AYU/Fq4GhbMd/ileIWZEtcdmIA==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "chalk": "^4.0.0", - "escalade": "^3.1.1", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "resolve": "^1.20.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.1.0.tgz", - "integrity": "sha512-Kq5XuDAELuBnrERrjFYEzu/A+i2W7l9HnPWqZEeKGEQ7m1R+6ndMbdXCVCx29Se1qwLZLgvoXwinB3SPIaitMQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-snapshot": "^27.1.0" - } - }, - "jest-runner": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.1.0.tgz", - "integrity": "sha512-ZWPKr9M5w5gDplz1KsJ6iRmQaDT/yyAFLf18fKbb/+BLWsR1sCNC2wMT0H7pP3gDcBz0qZ6aJraSYUNAGSJGaw==", - "dev": true, - "requires": { - "@jest/console": "^27.1.0", - "@jest/environment": "^27.1.0", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.0.6", - "jest-environment-jsdom": "^27.1.0", - "jest-environment-node": "^27.1.0", - "jest-haste-map": "^27.1.0", - "jest-leak-detector": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-resolve": "^27.1.0", - "jest-runtime": "^27.1.0", - "jest-util": "^27.1.0", - "jest-worker": "^27.1.0", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - } - }, - "jest-runtime": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.1.0.tgz", - "integrity": "sha512-okiR2cpGjY0RkWmUGGado6ETpFOi9oG3yV0CioYdoktkVxy5Hv0WRLWnJFuArSYS8cHMCNcceUUMGiIfgxCO9A==", - "dev": true, - "requires": { - "@jest/console": "^27.1.0", - "@jest/environment": "^27.1.0", - "@jest/fake-timers": "^27.1.0", - "@jest/globals": "^27.1.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.1.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-mock": "^27.1.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.1.0", - "jest-snapshot": "^27.1.0", - "jest-util": "^27.1.0", - "jest-validate": "^27.1.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.0.3" - } - }, - "jest-serializer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", - "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.1.0.tgz", - "integrity": "sha512-eaeUBoEjuuRwmiRI51oTldUsKOohB1F6fPqWKKILuDi/CStxzp2IWekVUXbuHHoz5ik33ioJhshiHpgPFbYgcA==", - "dev": true, - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.1.0", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.1.0", - "jest-get-type": "^27.0.6", - "jest-haste-map": "^27.1.0", - "jest-matcher-utils": "^27.1.0", - "jest-message-util": "^27.1.0", - "jest-resolve": "^27.1.0", - "jest-util": "^27.1.0", - "natural-compare": "^1.4.0", - "pretty-format": "^27.1.0", - "semver": "^7.3.2" - } - }, - "jest-util": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.1.0.tgz", - "integrity": "sha512-edSLD2OneYDKC6gZM1yc+wY/877s/fuJNoM1k3sOEpzFyeptSmke3SLnk1dDHk9CgTA+58mnfx3ew3J11Kes/w==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "picomatch": "^2.2.3" - } - }, - "jest-validate": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.1.0.tgz", - "integrity": "sha512-QiJ+4XuSuMsfPi9zvdO//IrSRSlG6ybJhOpuqYSsuuaABaNT84h0IoD6vvQhThBOKT+DIKvl5sTM0l6is9+SRA==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.1.0.tgz", - "integrity": "sha512-ivaWTrA46aHWdgPDgPypSHiNQjyKnLBpUIHeBaGg11U+pDzZpkffGlcB1l1a014phmG0mHgkOHtOgiqJQM6yKQ==", - "dev": true, - "requires": { - "@jest/test-result": "^27.1.0", - "@jest/types": "^27.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.1.0", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.1.0.tgz", - "integrity": "sha512-mO4PHb2QWLn9yRXGp7rkvXLAYuxwhq1ZYUo0LoDhg8wqvv4QizP1ZWEJOeolgbEgAWZLIEU0wsku8J+lGWfBhg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", - "dev": true - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, - "level": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-7.0.0.tgz", - "integrity": "sha512-QrBnjcWywalh86ms9hfizvxT5aBHrgWEu6rLChS9tFE2wwFU3aI1r0v+2SSZIyeUr4O4PFo8+sCc1kebahdhlw==", - "requires": { - "level-js": "^6.0.0", - "level-packager": "^6.0.0", - "leveldown": "^6.0.0" - } - }, - "level-codec": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-10.0.0.tgz", - "integrity": "sha512-QW3VteVNAp6c/LuV6nDjg7XDXx9XHK4abmQarxZmlRSDyXYk20UdaJTSX6yzVvQ4i0JyWSB7jert0DsyD/kk6g==", - "requires": { - "buffer": "^6.0.3" - } - }, - "level-concat-iterator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.0.0.tgz", - "integrity": "sha512-UHGiIdj+uiFQorOrURRvJF3Ei0uHc89ciM/aRi0qsWDV2f0HXypeXUPhJKL6DsONgSR76Pc0AI4sKYEYYRn2Dg==" - }, - "level-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-3.0.0.tgz", - "integrity": "sha512-MZXOQT061uEjxxxq4C/Jf+M3RdEKK9e3NbxlN7yOp1LDYoLVAhE2i1j0b7XqXfl8FjFtUL7phwr3Sn0wXXoMqA==", - "requires": { - "errno": "^1.0.0" - } - }, - "level-iterator-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-5.0.0.tgz", - "integrity": "sha512-wnb1+o+CVFUDdiSMR/ZymE2prPs3cjVLlXuDeSq9Zb8o032XrabGEXcTCsBxprAtseO3qvFeGzh6406z9sOTRA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "level-js": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/level-js/-/level-js-6.0.0.tgz", - "integrity": "sha512-7dp7JuaoQoqKW4ZGvrV1RB5f51/ktLdEo9fSDsh3Ofmg7sKCMu3X0CIngbY/IUz/YyskhN7LRvEVIkZHCY3LKQ==", - "requires": { - "abstract-leveldown": "^7.0.0", - "buffer": "^6.0.3", - "inherits": "^2.0.3", - "ltgt": "^2.1.2" - } - }, - "level-packager": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-6.0.0.tgz", - "integrity": "sha512-me656XRWfOVqs9wc+mWckZ6Rb1GuP33ndN4ZntDXwXFspX8cGA++Y+YqJsdE/mjTiipTuxXf047Z4rV62nOVuw==", - "requires": { - "encoding-down": "^7.0.0", - "levelup": "^5.0.0" - } - }, - "level-supports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.0.0.tgz", - "integrity": "sha512-8UJgzo1pvWP1wq80ZlkL19fPeK7tlyy0sBY90+2pj0x/kvzHCoLDWyuFJJMrsTn33oc7hbMkS3SkjCxMRPHWaw==" - }, - "leveldown": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.0.1.tgz", - "integrity": "sha512-D1fz5G58UrLBg5pXcN1pNxWtNLXwBiZsYt06XqjhD5zBHj/ws7xvZe3K0ZAhCbYuL9b4nbRCim8Z15HYWL2exQ==", - "requires": { - "abstract-leveldown": "^7.0.0", - "napi-macros": "~2.0.0", - "node-gyp-build": "~4.2.1" - } - }, - "levelup": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-5.0.1.tgz", - "integrity": "sha512-MJvQgBRQmB+E5+d6Qbxqm05N4U9NzOxGNhXx0rR8maRBwmVuVV+m4IV3N4HzZJW8JwiJ0jj92RZaytcD+Hr1CA==", - "requires": { - "catering": "^2.0.0", - "deferred-leveldown": "^6.0.0", - "level-errors": "^3.0.0", - "level-iterator-stream": "^5.0.0", - "level-supports": "^2.0.0", - "queue-microtask": "^1.2.3" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" - }, - "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "requires": { - "mime-db": "1.49.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, - "moment-timezone": { - "version": "0.5.33", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", - "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", - "requires": { - "moment": ">= 2.9.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "napi-macros": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-releases": { - "version": "1.1.75", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", - "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", - "dev": true - }, - "nodemon": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", - "integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==", - "dev": true, - "requires": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "requires": { - "boolbase": "^1.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "requires": { - "parse5": "^6.0.1" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, - "pretty-format": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.1.0.tgz", - "integrity": "sha512-4aGaud3w3rxAO6OXmK3fwBFQ0bctIOG3/if+jYEFGNGIs0EvuidQm3bZ9mlP2/t9epLNC/12czabfy7TZNSwVA==", - "dev": true, - "requires": { - "@jest/types": "^27.1.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "prism-media": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.2.tgz", - "integrity": "sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g==", - "requires": {} - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "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" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "require-all": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", - "integrity": "sha1-Rz1JcEvjEBFc4ST3c4Ox69hnExI=" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-encoding": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", - "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dev": true, - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - } - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", - "integrity": "sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz", - "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==", - "requires": {} - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 822a509..0000000 --- a/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "discord-free-games-notifier", - "version": "2.0.1", - "description": "A Discord bot that will notify when free games on Steam or Epic Games come out.", - "main": "./src/app.js", - "scripts": { - "test": "jest --watchAll --silent", - "test:showlogs": "jest --watchAll", - "test:once": "jest --silent", - "test:once-showlogs": "jest", - "start": "node ./src/app.js", - "debug": "node ./src/app.js --debug", - "dev": "NODE_ENV=development nodemon --exec node ./src/app.js", - "lint:errors-only": "eslint src test --quiet", - "lint": "eslint src test" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/moonstar-x/discord-free-games-notifier.git" - }, - "engines": { - "node": ">=12.0.0" - }, - "author": "moonstar-x", - "license": "MIT", - "bugs": { - "url": "https://github.com/moonstar-x/discord-free-games-notifier/issues" - }, - "homepage": "https://github.com/moonstar-x/discord-free-games-notifier#readme", - "dependencies": { - "@greencoast/discord.js-extended": "^1.2.0", - "@greencoast/logger": "^1.1.0", - "axios": "^0.21.1", - "cheerio": "^1.0.0-rc.10", - "cron": "^1.8.2", - "discord.js": "^12.5.3", - "level": "^7.0.0" - }, - "devDependencies": { - "@types/jest": "^27.0.1", - "eslint": "^7.32.0", - "eslint-config-greencoast": "0.0.2", - "jest": "^27.1.0", - "nodemon": "^2.0.12", - "text-encoding": "^0.7.0" - } -} diff --git a/src/app.js b/src/app.js deleted file mode 100644 index 1ee7c8f..0000000 --- a/src/app.js +++ /dev/null @@ -1,66 +0,0 @@ -const path = require('path'); -const { ExtendedClient, ConfigProvider } = require('@greencoast/discord.js-extended'); -const LevelDataProvider = require('@greencoast/discord.js-extended/dist/providers/LevelDataProvider').default; -const OffersNotifier = require('./classes/OffersNotifier'); -const { DEBUG_ENABLED } = require('./common/context'); - -const config = new ConfigProvider({ - configPath: path.join(__dirname, '../config/settings.json'), - env: process.env, - default: { - PREFIX: '$', - OWNER_ID: null, - OWNER_REPORTING: false, - PRESENCE_REFRESH_INTERVAL: 15 * 60 * 1000 // 15 Minutes - }, - types: { - TOKEN: 'string', - PREFIX: 'string', - OWNER_ID: ['string', 'null'], - OWNER_REPORTING: 'boolean', - PRESENCE_REFRESH_INTERVAL: ['number', 'null'] - } -}); - -const client = new ExtendedClient({ - config, - debug: DEBUG_ENABLED, - errorOwnerReporting: config.get('OWNER_REPORTING'), - owner: config.get('OWNER_ID'), - prefix: config.get('PREFIX'), - presence: { - refreshInterval: config.get('PRESENCE_REFRESH_INTERVAL'), - templates: [ - `{num_guilds} servers!`, - `{prefix}help for help.`, - `{num_members} users!`, - `up for {uptime}.` - ] - } -}); - -const provider = new LevelDataProvider(client, path.join(__dirname, '../data')); - -client - .registerDefaultEvents() - .registerExtraDefaultEvents(); - -client.registry - .registerGroups([ - ['misc', 'Miscellaneous Commands'], - ['config', 'Configuration Commands'] - ]) - .registerCommandsIn(path.join(__dirname, './commands')); - -client.on('ready', async() => { - await client.setDataProvider(provider); - - client.notifier = new OffersNotifier(client); - client.notifier.initialize(); -}); - -client.on('guildDelete', (guild) => { - client.dataProvider.clear(guild); -}); - -client.login(config.get('TOKEN')); diff --git a/src/classes/Cache.js b/src/classes/Cache.js deleted file mode 100644 index 9552935..0000000 --- a/src/classes/Cache.js +++ /dev/null @@ -1,39 +0,0 @@ -const logger = require('@greencoast/logger'); -const { DEBUG_ENABLED } = require('../common/context'); - -class Cache { - constructor(name) { - this.name = name; - this.content = null; - this.lastWriteTimestamp = null; - } - - shouldFetchFromCache() { - if (!this.lastWriteTimestamp) { - return false; - } - - return Date.now() < this.lastWriteTimestamp + Cache.MAX_AGE; - } - - get() { - if (DEBUG_ENABLED) { - logger.debug(`(CACHE): Read data for ${this.name} from cache.`); - } - - return this.content; - } - - set(content) { - if (DEBUG_ENABLED) { - logger.debug(`(CACHE): Wrote data for ${this.name} to cache.`); - } - - this.content = content; - this.lastWriteTimestamp = Date.now(); - } -} - -Cache.MAX_AGE = 900000; // 15 Minutes - -module.exports = Cache; diff --git a/src/classes/OffersCache.js b/src/classes/OffersCache.js deleted file mode 100644 index c2bb635..0000000 --- a/src/classes/OffersCache.js +++ /dev/null @@ -1,54 +0,0 @@ -const logger = require('@greencoast/logger'); -const { DEV_MODE } = require('../common/context'); - -class OffersCache { - constructor(dataProvider) { - this.dataProvider = dataProvider; - } - - async isOfferCached(offer) { - const cachedOffers = await this.dataProvider.getGlobal('notified', []); - - return cachedOffers.some((o) => o.id === offer.id); - } - - async update(currentOffers) { - const cachedOffers = await this.dataProvider.getGlobal('notified', []); - - const merged = this.mergeOffers(currentOffers, cachedOffers); - - if (DEV_MODE) { - logger.debug('Offers in cache:', merged); - } - - await this.dataProvider.setGlobal('notified', merged); - } - - mergeOffers(currentOffers, cachedOffers) { - const merged = []; - const maxAge = DEV_MODE ? OffersCache.DEV_MAX_AGE : OffersCache.MAX_AGE; - - for (const offer of cachedOffers) { - if (Date.now() < offer.lastFetched + maxAge) { - merged.push(offer); - } - } - - for (const offer of currentOffers) { - const offerInMergedIndex = merged.findIndex((o) => o.id === offer.id); - - if (offerInMergedIndex > -1) { - merged[offerInMergedIndex] = offer; - } else { - merged.push(offer); - } - } - - return merged; - } -} - -OffersCache.MAX_AGE = 24 * 60 * 60 * 1000; // 1 Day -OffersCache.DEV_MAX_AGE = 90000; // 1.5 Minutes - -module.exports = OffersCache; diff --git a/src/classes/OffersNotifier.js b/src/classes/OffersNotifier.js deleted file mode 100644 index 4e5a7a6..0000000 --- a/src/classes/OffersNotifier.js +++ /dev/null @@ -1,96 +0,0 @@ -const logger = require('@greencoast/logger'); -const { CronJob } = require('cron'); -const { CRON, GUILD_KEYS } = require('../common/constants'); -const { DEV_MODE } = require('../common/context'); -const ProviderFactory = require('./providers/ProviderFactory'); -const OffersCache = require('./OffersCache'); - -class OffersNotifier { - constructor(client) { - this.client = client; - this.notifyJob = null; - this.cache = new OffersCache(client.dataProvider); - } - - initialize() { - const self = this; - - this.notifyJob = new CronJob(DEV_MODE ? CRON.EVERY_MINUTE : CRON.EVERY_30_MINS, async function() { - logger.info('(CRON): Notifying enabled guilds...'); - return this.onComplete(await self.notify()); - }, function(notified) { - logger.info(notified ? '(CRON): Notification complete.' : '(CRON): Notification skipped, either there were no offers to notify or the current offers have been already notified.'); - logger.info(`(CRON): Next execution is scheduled for: ${this.nextDate().format(CRON.MOMENT_DATE_FORMAT)}`); - }, true); - - this.notifyJob.start(); - logger.info('(CRON): Notification job initialized.'); - logger.info(`(CRON): Next execution is scheduled for: ${this.notifyJob.nextDate().format(CRON.MOMENT_DATE_FORMAT)}`); - } - - getChannelsForEnabledGuilds() { - return this.client.guilds.cache.reduce(async(channels, guild) => { - const channelID = await this.client.dataProvider.get(guild, GUILD_KEYS.channel, null); - - if (channelID) { - const channel = guild.channels.cache.get(channelID); - - if (channel) { - return [...await channels, channel]; - } - } - - return channels; - }, []); - } - - filterValidOffers(allOffersByProvider) { - return allOffersByProvider.reduce((all, offers) => { - if (offers) { - all.push(...offers); - } - - return all; - }, []); - } - - async notify() { - const providers = ProviderFactory.getAll(); - const channels = await this.getChannelsForEnabledGuilds(); - const currentOffers = this.filterValidOffers(await Promise.all(providers.map((provider) => provider.getOffers()))); - - let atLeastOneOfferNotified = false; - - for (const offer of currentOffers) { - const notified = await this.notifySingleOffer(offer, channels); - - if (!atLeastOneOfferNotified) { - atLeastOneOfferNotified = notified; - } - } - - this.cache.update(currentOffers); - - return atLeastOneOfferNotified; - } - - async notifySingleOffer(offer, channels) { - const alreadyNotified = await this.cache.isOfferCached(offer); - - if (alreadyNotified) { - return false; - } - - channels.forEach((channel) => { - channel.send(`${offer.game} is free to keep on ${offer.provider}, you can grab it from here: ${offer.url}`) - .catch((error) => { - logger.error(`Something happened when trying to notify ${channel.name} from ${channel.guild.name}, perhaps I don't have enough permissions to send the message?`); - logger.error(error); - }); - }); - - return true; - } -} - -module.exports = OffersNotifier; diff --git a/src/classes/providers/AbstractProvider.js b/src/classes/providers/AbstractProvider.js deleted file mode 100644 index dd6e5a5..0000000 --- a/src/classes/providers/AbstractProvider.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable max-params */ -class AbstractProvider { - constructor() { - if (new.target === AbstractProvider) { - throw new TypeError('Cannot instantiate AbstractProvider!'); - } - } - - /** - * Return a Promise that resolves to the data received by the corresponding service. - * @returns {Promise<string|object>} - */ - getData() { - throw new Error('Method not implemented!'); - } - - /** - * Returns a Promise that resolves to an array of currently available offers. - * @returns {Promise<GameOffer[]>} - */ - getOffers() { - throw new Error('Method not implemented!'); - } - - /** - * Returns an object with the correct shape corresponding to a GameOffer. - * @param {...Arguments} _ The GameOffer properties. - * @returns {GameOffer} - */ - static createOffer(provider, game, url, id) { - return { - provider, - game, - url, - id, - lastFetched: Date.now() - }; - } -} - -module.exports = AbstractProvider; diff --git a/src/classes/providers/EpicGamesProvider.js b/src/classes/providers/EpicGamesProvider.js deleted file mode 100644 index d691094..0000000 --- a/src/classes/providers/EpicGamesProvider.js +++ /dev/null @@ -1,58 +0,0 @@ -const axios = require('axios'); -const logger = require('@greencoast/logger'); -const AbstractProvider = require('./AbstractProvider'); -const Cache = require('../Cache'); - -class EpicGamesProvider extends AbstractProvider { - constructor() { - super(); - - this.name = 'Epic Games'; - this.cache = new Cache(this.name); - } - - getData() { - return axios.get('https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions') - .then((res) => { - return res.data; - }) - .catch((error) => { - throw error; - }); - } - - getOffers() { - if (this.cache.shouldFetchFromCache()) { - return Promise.resolve(this.cache.get()); - } - - return this.getData() - .then((data) => { - const games = data.data.Catalog.searchStore.elements; - const offers = games.reduce((offers, game) => { - if (game.promotions && game.promotions.promotionalOffers && - game.promotions.promotionalOffers.length > 0 && - game.price.totalPrice.discountPrice === 0) { - let url = `https://epicgames.com/store/product/${game.productSlug}`; - - if (!url.endsWith('/home')) { - url += '/home'; - } - - offers.push(AbstractProvider.createOffer(this.name, game.title, url, game.productSlug)); - } - return offers; - }, []); - - this.cache.set(offers); - return offers; - }) - .catch((error) => { - logger.error(`Could not fetch offers from ${this.name}!`); - logger.error(error); - return null; - }); - } -} - -module.exports = EpicGamesProvider; diff --git a/src/classes/providers/ProviderFactory.js b/src/classes/providers/ProviderFactory.js deleted file mode 100644 index cad5176..0000000 --- a/src/classes/providers/ProviderFactory.js +++ /dev/null @@ -1,34 +0,0 @@ -const EpicGamesProvider = require('./EpicGamesProvider'); -const SteamProvider = require('./SteamProvider'); - -const epicGamesProvider = new EpicGamesProvider(); -const steamProvider = new SteamProvider(); - -class ProviderFactory { - static getAll() { - return [ - epicGamesProvider, - steamProvider - ]; - } - - static getInstance(name) { - switch (name) { - case ProviderFactory.providerNames.epic: - return epicGamesProvider; - - case ProviderFactory.providerNames.steam: - return steamProvider; - - default: - throw new TypeError(`No provider exists for ${name}!`); - } - } -} - -ProviderFactory.providerNames = { - steam: 'steam', - epic: 'epic' -}; - -module.exports = ProviderFactory; diff --git a/src/classes/providers/SteamProvider.js b/src/classes/providers/SteamProvider.js deleted file mode 100644 index f02c784..0000000 --- a/src/classes/providers/SteamProvider.js +++ /dev/null @@ -1,57 +0,0 @@ -const axios = require('axios'); -const cheerio = require('cheerio'); -const logger = require('@greencoast/logger'); -const AbstractProvider = require('./AbstractProvider'); -const Cache = require('../Cache'); - -const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'; - -class SteamProvider extends AbstractProvider { - constructor() { - super(); - - this.name = 'Steam'; - this.cache = new Cache(this.name); - } - - getData() { - return axios.get('https://store.steampowered.com/search/?maxprice=free&specials=1', { headers: { 'user-agent': UA } }) - .then((res) => { - return res.data; - }) - .catch((error) => { - throw error; - }); - } - - getOffers() { - if (this.cache.shouldFetchFromCache()) { - return Promise.resolve(this.cache.get()); - } - - return this.getData() - .then((html) => { - const $ = cheerio.load(html); - const games = $('#search_resultsRows').children(); - - const offers = []; - games.each((_, node) => { - const name = node.children[3].children[1].children[1].children[0].data; - const url = node.attribs.href; - const id = node.attribs['data-ds-appid']; - - offers.push(AbstractProvider.createOffer(this.name, name, url, id)); - }); - - this.cache.set(offers); - return offers; - }) - .catch((error) => { - logger.error(`Could not fetch offers from ${this.name}!`); - logger.error(error); - return null; - }); - } -} - -module.exports = SteamProvider; diff --git a/src/commands/config/DisableCommand.js b/src/commands/config/DisableCommand.js deleted file mode 100644 index d6d95bf..0000000 --- a/src/commands/config/DisableCommand.js +++ /dev/null @@ -1,32 +0,0 @@ -const { Command } = require('@greencoast/discord.js-extended'); -const logger = require('@greencoast/logger'); -const { GUILD_KEYS } = require('../../common/constants'); - -class DisableCommand extends Command { - constructor(client) { - super(client, { - name: 'disable', - description: 'Disable free game announcements on this server.', - emoji: ':no_entry_sign:', - group: 'config', - guildOnly: true, - ownerOverride: false, - userPermissions: ['MANAGE_CHANNELS'] - }); - } - - async run(message) { - const currentChannel = await this.client.dataProvider.get(message.guild, GUILD_KEYS.channel, null); - - if (!currentChannel) { - return message.reply('no announcement channel is currently set.'); - } - - await this.client.dataProvider.set(message.guild, GUILD_KEYS.channel, null); - - logger.info(`Announcements have been disabled for ${message.guild.name}.`); - return message.channel.send('Announcements have been disabled on this server.'); - } -} - -module.exports = DisableCommand; diff --git a/src/commands/config/SetChannelCommand.js b/src/commands/config/SetChannelCommand.js deleted file mode 100644 index a10415e..0000000 --- a/src/commands/config/SetChannelCommand.js +++ /dev/null @@ -1,52 +0,0 @@ -const { Command } = require('@greencoast/discord.js-extended'); -const logger = require('@greencoast/logger'); -const { GUILD_KEYS } = require('../../common/constants'); - -class SetChannelCommand extends Command { - constructor(client) { - super(client, { - name: 'setchannel', - aliases: ['channel'], - description: 'Set the channel to which the bot will announce free game offers.', - emoji: ':loudspeaker:', - group: 'config', - guildOnly: true, - ownerOverride: false, - userPermissions: ['MANAGE_CHANNELS'] - }); - } - - async updateChannel(message, channel) { - const previousChannelID = await this.client.dataProvider.get(message.guild, GUILD_KEYS.channel); - - if (previousChannelID === channel.id) { - return message.reply('the channel you mentioned is already set as the announcement channel.'); - } - - try { - await this.client.dataProvider.set(message.guild, GUILD_KEYS.channel, channel.id); - - logger.info(`Announcement channel changed for ${message.guild.name} to #${channel.name}.`); - return message.channel.send(`The announcement channel has been changed to ${channel}.`); - } catch (error) { - logger.error(error); - return message.channel.send('Something happened when trying to update the announcement channel.'); - } - } - - run(message) { - const channel = message.mentions.channels.first(); - - if (!channel) { - return message.reply(`you need to mention the channel you wish to set.`); - } - - if (!channel.viewable) { - return message.reply(`I cannot see the channel you mentioned, do I have enough permissions to access it?`); - } - - return this.updateChannel(message, channel); - } -} - -module.exports = SetChannelCommand; diff --git a/src/commands/misc/HelpCommand.js b/src/commands/misc/HelpCommand.js deleted file mode 100644 index 62267f2..0000000 --- a/src/commands/misc/HelpCommand.js +++ /dev/null @@ -1,45 +0,0 @@ -const { Command } = require('@greencoast/discord.js-extended'); -const { MessageEmbed } = require('discord.js'); -const { MESSAGE_EMBED } = require('../../common/constants'); - -class HelpCommand extends Command { - constructor(client) { - super(client, { - name: 'help', - aliases: ['h'], - description: 'Get a description of all the commands you can use.', - emoji: ':question:', - group: 'misc', - guildOnly: false - }); - } - - prepareFields() { - return this.client.registry.groups.map((group) => { - const listOfCommands = group.commands.reduce((text, command) => { - return text.concat(`${command.emoji} **${this.client.prefix}${command.name}** - ${command.description}\n`); - }, ''); - - return { title: group.name, text: listOfCommands }; - }); - } - - run(message) { - const fields = this.prepareFields(); - const embed = new MessageEmbed() - .setTitle('Free Games Notifier Help Message') - .setColor(MESSAGE_EMBED.color) - .setThumbnail(MESSAGE_EMBED.thumbnail); - - for (const key in fields) { - const field = fields[key]; - embed.addField(field.title, field.text); - } - - embed.addField('Found a bug?', `This bot is far from perfect, so in case you found a bug, please report it [here](${MESSAGE_EMBED.issuesURL}).`); - - return message.channel.send(embed); - } -} - -module.exports = HelpCommand; diff --git a/src/commands/misc/OffersCommand.js b/src/commands/misc/OffersCommand.js deleted file mode 100644 index a88227a..0000000 --- a/src/commands/misc/OffersCommand.js +++ /dev/null @@ -1,73 +0,0 @@ -const { Command } = require('@greencoast/discord.js-extended'); -const ProviderFactory = require('../../classes/providers/ProviderFactory'); - -class OffersCommand extends Command { - constructor(client) { - super(client, { - name: 'offers', - description: 'Get the current offers on the supported storefronts.', - emoji: ':moneybag:', - group: 'misc', - guildOnly: false - }); - } - - prepareMessageForOffer(header, offers) { - return offers.reduce((message, offer, i) => { - return `${message}${i + 1}. ${offer.game} - available at: ${offer.url}\n`; - }, `${header}\n\n`); - } - - async handleAllProviders(message) { - const providers = ProviderFactory.getAll(); - - for (const provider of providers) { - const offers = await provider.getOffers(); - - if (!offers) { - message.channel.send(`Something happened when looking for offers in ${provider.name}. Try again later.`); - continue; - } - - if (offers.length < 1) { - continue; - } - - message.channel.send(this.prepareMessageForOffer(`Here are the offers available currently for ${provider.name}:`, offers)); - } - } - - async handleSingleProvider(message, providerName) { - try { - const provider = ProviderFactory.getInstance(providerName); - const offers = await provider.getOffers(); - - if (!offers) { - return message.channel.send(`Something happened when looking for offers in ${provider.name}. Try again later.`); - } - - if (offers.length < 1) { - return message.channel.send(`There are no free games currently in ${provider.name}. :(`); - } - - return message.channel.send(this.prepareMessageForOffer(`Here are the offers available currently for ${provider.name}:`, offers)); - } catch (error) { - if (error instanceof TypeError) { - const availableProviders = Object.values(ProviderFactory.providerNames).join(', '); - return message.channel.send(`I don't know of any game storefront named ${providerName}. Try again with any of the following: **${availableProviders}**`); - } - - throw error; - } - } - - run(message, [providerName]) { - if (!providerName) { - return this.handleAllProviders(message); - } - - return this.handleSingleProvider(message, providerName); - } -} - -module.exports = OffersCommand; diff --git a/src/common/constants.js b/src/common/constants.js deleted file mode 100644 index 1b43566..0000000 --- a/src/common/constants.js +++ /dev/null @@ -1,21 +0,0 @@ -const MESSAGE_EMBED = { - color: '#43aa8b', - thumbnail: 'https://i.imgur.com/Tqnk48j.png', - issuesURL: 'https://github.com/moonstar-x/discord-free-games-notifier/issues' -}; - -const CRON = { - EVERY_MINUTE: '* * * * *', - EVERY_30_MINS: '*/30 * * * *', - MOMENT_DATE_FORMAT: 'ddd, D/M/Y @hh:mm:ss a' -}; - -const GUILD_KEYS = { - channel: 'channel' -}; - -module.exports = { - MESSAGE_EMBED, - CRON, - GUILD_KEYS -}; diff --git a/src/common/context.js b/src/common/context.js deleted file mode 100644 index 89f4696..0000000 --- a/src/common/context.js +++ /dev/null @@ -1,8 +0,0 @@ -const DEBUG_ENABLED = process.argv.includes('--debug'); - -const DEV_MODE = process.env.NODE_ENV === 'development'; - -module.exports = { - DEBUG_ENABLED, - DEV_MODE -}; diff --git a/test/classes/Cache.spec.js b/test/classes/Cache.spec.js deleted file mode 100644 index 5165c91..0000000 --- a/test/classes/Cache.spec.js +++ /dev/null @@ -1,84 +0,0 @@ -const logger = require('@greencoast/logger'); -const Cache = require('../../src/classes/Cache'); - -let cache; - -jest.mock('@greencoast/logger'); - -const nowSpy = jest.spyOn(Date, 'now'); -const mockedTimestamp = 1609573038753; -nowSpy.mockReturnValue(mockedTimestamp); - -describe('Classes - Cache', () => { - beforeEach(() => { - cache = new Cache(); - logger.debug.mockClear(); - }); - - it('should have a name property.', () => { - expect(cache).toHaveProperty('name'); - }); - - it('should have a content property.', () => { - expect(cache).toHaveProperty('content'); - }); - - it('should have a lastWriteTimestamp property.', () => { - expect(cache).toHaveProperty('lastWriteTimestamp'); - }); - - describe('get()', () => { - it('should return content.', () => { - cache.content = 'new content'; - expect(cache.get()).toBe(cache.content); - }); - - it('should not log the operation if debug is disabled.', () => { - cache.get(); - expect(logger.debug).not.toHaveBeenCalled(); - }); - }); - - describe('set()', () => { - it('should set content.', () => { - cache.set('new content'); - expect(cache.content).toBe('new content'); - }); - - it('should update the lastWriteTimestamp property.', () => { - cache.set('new content'); - expect(cache.lastWriteTimestamp).toBe(mockedTimestamp); - }); - - it('should not log the operation if debug is disabled.', () => { - cache.set('new content'); - expect(logger.debug).not.toHaveBeenCalled(); - }); - }); - - describe('shouldFetchFromCache()', () => { - it('should return false if lastWriteTimestamp is null.', () => { - expect(cache.shouldFetchFromCache()).toBe(false); - }); - - it('should return false if content is old.', () => { - cache.set('old content'); - nowSpy.mockReturnValueOnce(mockedTimestamp + Cache.MAX_AGE + 5000); - - expect(cache.shouldFetchFromCache()).toBe(false); - }); - - it('should return true if content is still valid.', () => { - cache.set('old content'); - nowSpy.mockReturnValueOnce(mockedTimestamp + Cache.MAX_AGE - 5000); - - expect(cache.shouldFetchFromCache()).toBe(true); - }); - }); - - describe('static MAX_AGE', () => { - it('should be a number.', () => { - expect(Cache.MAX_AGE).not.toBeNaN(); - }); - }); -}); diff --git a/test/classes/OffersCache.spec.js b/test/classes/OffersCache.spec.js deleted file mode 100644 index 3d0f465..0000000 --- a/test/classes/OffersCache.spec.js +++ /dev/null @@ -1,130 +0,0 @@ -const OffersCache = require('../../src/classes/OffersCache'); -const { clientMock } = require('../../__mocks__/discordMocks'); - -const nowSpy = jest.spyOn(Date, 'now'); - -describe('Classes - OffersCache', () => { - let cache; - - beforeEach(() => { - cache = new OffersCache(clientMock.dataProvider); - - clientMock.dataProvider.getGlobal.mockClear(); - clientMock.dataProvider.setGlobal.mockClear(); - }); - - describe('isOfferCached()', () => { - it('should return true if offer is in the cache.', () => { - clientMock.dataProvider.getGlobal.mockResolvedValueOnce([{ id: '123' }]); - - return cache.isOfferCached({ id: '123' }) - .then((result) => { - expect(result).toBe(true); - }); - }); - - it('should return false if offer is not in the cache.', () => { - clientMock.dataProvider.getGlobal.mockResolvedValueOnce([{ id: '234' }]); - - return cache.isOfferCached({ id: '123' }) - .then((result) => { - expect(result).toBe(false); - }); - }); - }); - - describe('update()', () => { - it('should update the cache with merged offers.', () => { - const expectedOffers = [{ id: '123' }, { id: '234' }]; - jest.spyOn(cache, 'mergeOffers').mockReturnValueOnce(expectedOffers); - - return cache.update() - .then(() => { - expect(clientMock.dataProvider.setGlobal).toHaveBeenCalledWith('notified', expectedOffers); - }); - }); - }); - - describe('mergeOffers()', () => { - it('should return an array containing the past offers that are still valid.', () => { - nowSpy.mockReturnValue(1000); - - const cachedOffers = [ - { - id: '123', - lastFetched: 2000 - }, - { - id: '234', - lastFetched: 3000 - } - ]; - - const merged = cache.mergeOffers([], cachedOffers); - - expect(merged).toContain(cachedOffers[0]); - expect(merged).toContain(cachedOffers[1]); - }); - - it('should return an array without invalid offers.', () => { - nowSpy.mockReturnValue(100000000000); - - const cachedOffers = [ - { - id: '123', - lastFetched: 100000000000 - }, - { - id: '234', - lastFetched: 100 - } - ]; - - const merged = cache.mergeOffers([], cachedOffers); - - expect(merged).toContain(cachedOffers[0]); - expect(merged).not.toContain(cachedOffers[1]); - }); - - it('should return an array with all new offers.', () => { - const currentOffers = [ - { - id: '123', - lastFetched: 100000000000 - }, - { - id: '234', - lastFetched: 100 - } - ]; - - const merged = cache.mergeOffers(currentOffers, []); - - expect(merged).toContain(currentOffers[0]); - expect(merged).toContain(currentOffers[1]); - }); - - it('should return an array where the new offers override the old ones.', () => { - const currentOffers = [ - { - id: '123', - lastFetched: 2222222222 - }, - { - id: '234', - lastFetched: 100 - } - ]; - const cachedOffer = { - id: '123', - lastFetched: 11111111111 - }; - - const merged = cache.mergeOffers(currentOffers, [cachedOffer]); - - expect(merged).toContain(currentOffers[0]); - expect(merged).toContain(currentOffers[1]); - expect(merged).not.toContain(cachedOffer); - }); - }); -}); diff --git a/test/classes/OffersNotifier.spec.js b/test/classes/OffersNotifier.spec.js deleted file mode 100644 index 719c56a..0000000 --- a/test/classes/OffersNotifier.spec.js +++ /dev/null @@ -1,207 +0,0 @@ -const OffersNotifier = require('../../src/classes/OffersNotifier'); -const cron = require('cron'); -const logger = require('@greencoast/logger'); -const ProviderFactory = require('../../src/classes/providers/ProviderFactory'); -const { clientMock, channelMock } = require('../../__mocks__/discordMocks'); -const { providerMock, offerMock } = require('../../__mocks__/providers'); - -jest.mock('cron'); -jest.mock('@greencoast/logger'); -jest.mock('../../src/classes/providers/ProviderFactory'); - -describe('Classes - OffersNotifier', () => { - let notifier; - - beforeAll(() => { - ProviderFactory.getAll.mockReturnValue([providerMock, providerMock]); - ProviderFactory.getInstance.mockImplementation((name) => { - if (name === 'valid') { - return providerMock; - } - throw new TypeError('Invalid provider'); - }); - }); - - beforeEach(() => { - notifier = new OffersNotifier(clientMock); - - logger.info.mockClear(); - - channelMock.send.mockClear(); - clientMock.dataProvider.clearGlobal.mockClear(); - clientMock.dataProvider.setGlobal.mockClear(); - clientMock.dataProvider.getGlobal.mockClear(); - - clientMock.dataProvider.getGlobal.mockResolvedValue([]); - }); - - describe('initialize()', () => { - let notifySpy; - - beforeEach(() => { - notifySpy = jest.spyOn(notifier, 'notify') - .mockResolvedValue(true); - }); - - it('should set the notifyJob property.', () => { - notifier.initialize(); - - expect(notifier.notifyJob).toBeInstanceOf(cron.CronJob); - }); - - it('should start the cronjob and log it.', () => { - notifier.initialize(); - - expect(notifier.notifyJob.start).toHaveBeenCalledTimes(1); - expect(logger.info).toHaveBeenCalled(); - expect(logger.info).toHaveBeenCalledWith('(CRON): Notification job initialized.'); - expect(logger.info).toHaveBeenCalledWith('(CRON): Next execution is scheduled for: time'); - }); - - it('should log when running the cronjob.', () => { - notifier.initialize(); - - return notifier.notifyJob.start() - .then(() => { - expect(logger.info).toHaveBeenCalled(); - expect(logger.info).toHaveBeenCalledWith('(CRON): Notifying enabled guilds...'); - }); - }); - - it('should call notify when running the cronjob.', () => { - notifier.initialize(); - - return notifier.notifyJob.start() - .then(() => { - expect(notifySpy).toHaveBeenCalled(); - }); - }); - - it('should log if notifications were sent on completion.', () => { - notifier.initialize(); - - return notifier.notifyJob.start() - .then(() => { - expect(logger.info).toHaveBeenCalled(); - expect(logger.info).toHaveBeenCalledWith('(CRON): Notification complete.'); - expect(logger.info).toHaveBeenCalledWith('(CRON): Next execution is scheduled for: time'); - }); - }); - - it('should log if notification was skipped on completion.', () => { - notifySpy.mockResolvedValue(false); - notifier.initialize(); - - return notifier.notifyJob.start() - .then(() => { - expect(logger.info).toHaveBeenCalled(); - expect(logger.info).toHaveBeenCalledWith('(CRON): Notification skipped, either there were no offers to notify or the current offers have been already notified.'); - expect(logger.info).toHaveBeenCalledWith('(CRON): Next execution is scheduled for: time'); - }); - }); - }); - - describe('getChannelsForEnabledGuilds()', () => { - it('should resolve an Array of Channels.', () => { - clientMock.dataProvider.get.mockResolvedValue(true); - - return notifier.getChannelsForEnabledGuilds() - .then((channels) => { - expect(channels).toBeInstanceOf(Array); - - channels.forEach((channel) => { - expect(channel.id).toBe(channelMock.id); - }); - }); - }); - }); - - describe('filterValidOffers()', () => { - it('should filter out offers that are null.', () => { - const filtered = notifier.filterValidOffers([[offerMock], null, [offerMock, offerMock]]); - - expect(filtered).toHaveLength(3); - }); - - it('should filter out offers that are empty.', () => { - const filtered = notifier.filterValidOffers([[offerMock], [], [offerMock, offerMock]]); - - expect(filtered).toHaveLength(3); - }); - - it('should return a flattened array with all the offers.', () => { - const filtered = notifier.filterValidOffers([[offerMock], null, [offerMock, offerMock]]); - - expect(filtered).toEqual([offerMock, offerMock, offerMock]); - }); - }); - - describe('notify()', () => { - beforeEach(() => { - jest.spyOn(notifier, 'getChannelsForEnabledGuilds') - .mockResolvedValue([channelMock, channelMock]); - }); - - it('should resolve false if no offers are notified.', () => { - jest.spyOn(notifier, 'notifySingleOffer').mockResolvedValue(false); - - return notifier.notify() - .then((result) => { - expect(result).toBe(false); - }); - }); - - it('should resolve true if at least an offer is notified.', () => { - jest.spyOn(notifier, 'notifySingleOffer').mockResolvedValue(true); - - return notifier.notify() - .then((result) => { - expect(result).toBe(true); - }); - }); - }); - - describe('notifySingleOffer()', () => { - it('should resolve false if the offer is already notified.', () => { - jest.spyOn(notifier.cache, 'isOfferCached').mockResolvedValue(true); - - return notifier.notifySingleOffer(offerMock, [channelMock]) - .then((result) => { - expect(result).toBe(false); - }); - }); - - it('should skip the notification if offer is already notified.', () => { - jest.spyOn(notifier.cache, 'isOfferCached').mockResolvedValue(true); - - return notifier.notifySingleOffer(offerMock, [channelMock]) - .then(() => { - expect(channelMock.send).not.toHaveBeenCalled(); - }); - }); - - it('should send a notification for the offer.', () => { - jest.spyOn(notifier.cache, 'isOfferCached').mockResolvedValue(false); - - return notifier.notifySingleOffer(offerMock, [channelMock]) - .then(() => { - expect(channelMock.send).toHaveBeenCalled(); - expect(channelMock.send.mock.calls[0][0]).toContain(offerMock.game); - }); - }); - - it('should log an error if channel.send rejects.', () => { - const expectedError = new Error('Oops'); - - jest.spyOn(notifier.cache, 'isOfferCached').mockResolvedValue(false); - channelMock.send.mockRejectedValueOnce(expectedError); - - return notifier.notifySingleOffer(offerMock, [channelMock]) - .then(() => { - expect(logger.error).toHaveBeenCalled(); - expect(logger.error).toHaveBeenCalledWith(expectedError); - expect(logger.error.mock.calls[0][0]).toContain('Something happened'); - }); - }); - }); -}); diff --git a/test/classes/providers/AbstractProvider.spec.js b/test/classes/providers/AbstractProvider.spec.js deleted file mode 100644 index 9b9cfb6..0000000 --- a/test/classes/providers/AbstractProvider.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable no-new */ -const AbstractProvider = require('../../../src/classes/providers/AbstractProvider'); - -jest.spyOn(Date, 'now').mockReturnValue(1000); - -describe('Classes - Providers - AbstractProvider', () => { - it('should throw if trying to instantiate.', () => { - expect(() => { - new AbstractProvider(); - }).toThrow(TypeError); - }); - - describe('static createOffer', () => { - it('should return an object with the correct shape.', () => { - const expected = { - provider: 'provider', - game: 'game', - url: 'url', - id: 'id', - lastFetched: 1000 - }; - - expect(AbstractProvider.createOffer('provider', 'game', 'url', 'id')).toStrictEqual(expected); - }); - }); -}); diff --git a/test/classes/providers/EpicGamesProvider.spec.js b/test/classes/providers/EpicGamesProvider.spec.js deleted file mode 100644 index 0a52896..0000000 --- a/test/classes/providers/EpicGamesProvider.spec.js +++ /dev/null @@ -1,162 +0,0 @@ -const AbstractProvider = require('../../../src/classes/providers/AbstractProvider'); -const EpicGamesProvider = require('../../../src/classes/providers/EpicGamesProvider'); -const Cache = require('../../../src/classes/Cache'); -const axios = require('axios'); -const logger = require('@greencoast/logger'); - -jest.mock('axios'); -jest.mock('@greencoast/logger'); - -jest.spyOn(Date, 'now').mockReturnValue(1000); - -const mockedGame = { - title: 'game', - productSlug: 'slug', - price: { - totalPrice: { - discountPrice: 0 - } - }, - promotions: { - promotionalOffers: [ - '1 promotion' - ] - } -}; - -const mockedData = { - data: { - Catalog: { - searchStore: { - elements: [ - mockedGame - ] - } - } - } -}; - -describe('Classes - Providers - EpicGamesProvider', () => { - let provider; - - beforeAll(() => { - axios.get.mockImplementation(() => Promise.resolve({ data: mockedData })); - }); - - beforeEach(() => { - provider = new EpicGamesProvider(); - axios.get.mockClear(); - logger.error.mockClear(); - }); - - it('should be instance of AbstractProvider.', () => { - expect(provider).toBeInstanceOf(AbstractProvider); - }); - - it('should contain a name property.', () => { - expect(provider).toHaveProperty('name', 'Epic Games'); - }); - - it('should contain a cache property.', () => { - expect(provider).toHaveProperty('cache'); - expect(provider.cache).toBeInstanceOf(Cache); - }); - - describe('getData()', () => { - it('should return a Promise.', () => { - expect(provider.getData()).toBeInstanceOf(Promise); - }); - - it('should resolve a data object.', () => { - return provider.getData() - .then((data) => { - expect(data).toBe(mockedData); - }); - }); - - it('should reject if axios.get rejects.', () => { - const expectedError = new Error('Oops'); - axios.get.mockRejectedValueOnce(expectedError); - expect.assertions(1); - - return provider.getData() - .catch((error) => { - expect(error).toBe(expectedError); - }); - }); - }); - - describe('getOffers()', () => { - let expectedOffer; - - beforeAll(() => { - expectedOffer = { - provider: provider.name, - game: mockedGame.title, - url: `https://epicgames.com/store/product/${mockedGame.productSlug}/home`, - id: mockedGame.productSlug, - lastFetched: 1000 - }; - }); - - it('should return a Promise.', () => { - expect(provider.getOffers()).toBeInstanceOf(Promise); - }); - - it('should resolve an array of GameOffers.', () => { - return provider.getOffers() - .then((offers) => { - expect(offers).toBeInstanceOf(Array); - expect(offers).toContainEqual(expectedOffer); - }); - }); - - it('should resolve an array of correct GameOffers if productSlug ends with /home.', () => { - axios.get.mockImplementationOnce(() => { - const newMockedData = { - ...mockedData, - productSlug: 'slug/home' - }; - - return Promise.resolve({ data: newMockedData }); - }); - - return provider.getOffers() - .then((offers) => { - expect(offers).toBeInstanceOf(Array); - expect(offers).toContainEqual(expectedOffer); - }); - }); - - it('should resolve null if getData rejects.', () => { - axios.get.mockRejectedValueOnce(new Error()); - return provider.getOffers() - .then((offers) => { - expect(offers).toBeNull(); - }); - }); - - it('should log that the fetch did not work if getData rejects.', () => { - const expectedError = new Error('Oops'); - axios.get.mockRejectedValueOnce(expectedError); - - return provider.getOffers() - .then(() => { - expect(logger.error).toBeCalledTimes(2); - expect(logger.error).toHaveBeenCalledWith(`Could not fetch offers from ${provider.name}!`); - expect(logger.error).toHaveBeenCalledWith(expectedError); - }); - }); - - it('should read from the cache if called repeatedly.', () => { - return provider.getOffers() - .then(() => { - return provider.getOffers() - .then((offers) => { - expect(axios.get).toBeCalledTimes(1); - expect(offers).toContainEqual(expectedOffer); - }); - }); - }); - }); -}); diff --git a/test/classes/providers/ProviderFactory.spec.js b/test/classes/providers/ProviderFactory.spec.js deleted file mode 100644 index 2be1f8f..0000000 --- a/test/classes/providers/ProviderFactory.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -const ProviderFactory = require('../../../src/classes/providers/ProviderFactory'); -const SteamProvider = require('../../../src/classes/providers/SteamProvider'); -const EpicGamesProvider = require('../../../src/classes/providers/EpicGamesProvider'); -const AbstractProvider = require('../../../src/classes/providers/AbstractProvider'); - -describe('Classes - Providers - ProviderFactory', () => { - describe('static getAll()', () => { - it('should return an Array.', () => { - expect(ProviderFactory.getAll()).toBeInstanceOf(Array); - }); - - it('should return an array of provider objects.', () => { - ProviderFactory.getAll().forEach((provider) => { - expect(provider).toBeInstanceOf(AbstractProvider); - }); - }); - }); - - describe('static getInstance()', () => { - it('should return a SteamProvider if name is steam.', () => { - expect(ProviderFactory.getInstance('steam')).toBeInstanceOf(SteamProvider); - }); - - it('should return an EpicGamesProvider if name is epic.', () => { - expect(ProviderFactory.getInstance('epic')).toBeInstanceOf(EpicGamesProvider); - }); - - it('should throw if name is not valid.', () => { - expect(() => { - ProviderFactory.getInstance('any'); - }).toThrow(TypeError); - }); - }); - - describe('static providerNames', () => { - it('should have a steam property.', () => { - expect(ProviderFactory.providerNames).toHaveProperty('steam'); - }); - - it('should have a epic property.', () => { - expect(ProviderFactory.providerNames).toHaveProperty('epic'); - }); - }); -}); diff --git a/test/classes/providers/SteamProvider.spec.js b/test/classes/providers/SteamProvider.spec.js deleted file mode 100644 index a9244e5..0000000 --- a/test/classes/providers/SteamProvider.spec.js +++ /dev/null @@ -1,122 +0,0 @@ -const AbstractProvider = require('../../../src/classes/providers/AbstractProvider'); -const SteamProvider = require('../../../src/classes/providers/SteamProvider'); -const Cache = require('../../../src/classes/Cache'); -const axios = require('axios'); -const logger = require('@greencoast/logger'); -const fs = require('fs'); -const path = require('path'); - -jest.mock('axios'); -jest.mock('@greencoast/logger'); - -jest.spyOn(Date, 'now').mockReturnValue(1000); - -const mockedHTML = fs.readFileSync(path.join(__dirname, '../../../__mocks__/steamPage')); - -describe('Classes - Providers - SteamProvider', () => { - let provider; - - beforeAll(() => { - axios.get.mockImplementation(() => Promise.resolve({ data: mockedHTML })); - }); - - beforeEach(() => { - provider = new SteamProvider(); - axios.get.mockClear(); - logger.error.mockClear(); - }); - - it('should be instance of AbstractProvider.', () => { - expect(provider).toBeInstanceOf(AbstractProvider); - }); - - it('should contain a name property.', () => { - expect(provider).toHaveProperty('name', 'Steam'); - }); - - it('should contain a cache property.', () => { - expect(provider).toHaveProperty('cache'); - expect(provider.cache).toBeInstanceOf(Cache); - }); - - describe('getData()', () => { - it('should return a Promise.', () => { - expect(provider.getData()).toBeInstanceOf(Promise); - }); - - it('should resolve raw HTML.', () => { - return provider.getData() - .then((data) => { - expect(data).toBe(mockedHTML); - }); - }); - - it('should reject if axios.get rejects.', () => { - const expectedError = new Error('Oops'); - axios.get.mockRejectedValueOnce(expectedError); - expect.assertions(1); - - return provider.getData() - .catch((error) => { - expect(error).toBe(expectedError); - }); - }); - - describe('getOffers()', () => { - let expectedOffer; - - beforeAll(() => { - expectedOffer = { - provider: provider.name, - game: 'Battlefield 1 Shortcut Kit: Infantry Bundle', - url: 'https://store.steampowered.com/app/1314764/Battlefield_1_Shortcut_Kit_Infantry_Bundle/?snr=1_7_7_2300_150_1', - id: '1314764', - lastFetched: 1000 - }; - }); - - it('should return a Promise.', () => { - expect(provider.getOffers()).toBeInstanceOf(Promise); - }); - - it('should resolve an array of GameOffers.', () => { - return provider.getOffers() - .then((offers) => { - expect(offers).toBeInstanceOf(Array); - expect(offers).toContainEqual(expectedOffer); - }); - }); - - it('should resolve null if getData rejects.', () => { - axios.get.mockRejectedValueOnce(new Error()); - return provider.getOffers() - .then((offers) => { - expect(offers).toBeNull(); - }); - }); - - it('should log that the fetch did not work if getData rejects.', () => { - const expectedError = new Error('Oops'); - axios.get.mockRejectedValueOnce(expectedError); - - return provider.getOffers() - .then(() => { - expect(logger.error).toBeCalledTimes(2); - expect(logger.error).toHaveBeenCalledWith(`Could not fetch offers from ${provider.name}!`); - expect(logger.error).toHaveBeenCalledWith(expectedError); - }); - }); - - it('should read from the cache if called repeatedly.', () => { - return provider.getOffers() - .then(() => { - return provider.getOffers() - .then((offers) => { - expect(axios.get).toBeCalledTimes(1); - expect(offers).toContainEqual(expectedOffer); - }); - }); - }); - }); - }); -}); diff --git a/test/commands/config/DisableCommand.spec.js b/test/commands/config/DisableCommand.spec.js deleted file mode 100644 index 8a1e402..0000000 --- a/test/commands/config/DisableCommand.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -const DisableCommand = require('../../../src/commands/config/DisableCommand'); -const logger = require('@greencoast/logger'); -const { clientMock, messageMock } = require('../../../__mocks__/discordMocks'); - -jest.mock('@greencoast/logger'); - -describe('Commands - DisableCommand', () => { - let command; - - beforeEach(() => { - command = new DisableCommand(clientMock); - - logger.info.mockClear(); - messageMock.channel.send.mockClear(); - messageMock.reply.mockClear(); - clientMock.dataProvider.get.mockClear(); - clientMock.dataProvider.set.mockClear(); - }); - - describe('run()', () => { - it('should reply if no channel is saved in the database.', () => { - clientMock.dataProvider.get.mockResolvedValueOnce(null); - - return command.run(messageMock) - .then(() => { - expect(messageMock.reply).toHaveBeenCalledTimes(1); - expect(messageMock.reply.mock.calls[0][0]).toContain('no announcement'); - }); - }); - - it('should set the channel to null for the guild.', () => { - clientMock.dataProvider.get.mockResolvedValueOnce('channel'); - - return command.run(messageMock) - .then(() => { - expect(clientMock.dataProvider.set).toHaveBeenCalledWith(messageMock.guild, 'channel', null); - }); - }); - - it('should log that the channel has been removed for the guild.', () => { - clientMock.dataProvider.get.mockResolvedValueOnce('channel'); - - return command.run(messageMock) - .then(() => { - expect(logger.info).toHaveBeenCalledTimes(1); - expect(logger.info.mock.calls[0][0]).toContain('disabled'); - }); - }); - - it('should send a message that the channel has been removed for the guild.', () => { - clientMock.dataProvider.get.mockResolvedValueOnce('channel'); - - return command.run(messageMock) - .then(() => { - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - expect(messageMock.channel.send.mock.calls[0][0]).toContain('disabled'); - }); - }); - }); -}); diff --git a/test/commands/config/SetChannelCommand.spec.js b/test/commands/config/SetChannelCommand.spec.js deleted file mode 100644 index 4ad21d3..0000000 --- a/test/commands/config/SetChannelCommand.spec.js +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint-disable max-len */ -const logger = require('@greencoast/logger'); -const SetChannelCommand = require('../../../src/commands/config/SetChannelCommand'); -const { GUILD_KEYS } = require('../../../src/common/constants'); -const { clientMock, messageMock, channelMock } = require('../../../__mocks__/discordMocks'); - -jest.mock('@greencoast/logger'); - -let command; - -describe('Commands - SetChannelCommand', () => { - beforeEach(() => { - logger.info.mockClear(); - logger.error.mockClear(); - messageMock.channel.send.mockClear(); - messageMock.reply.mockClear(); - clientMock.dataProvider.get.mockClear(); - clientMock.dataProvider.set.mockClear(); - - command = new SetChannelCommand(clientMock); - }); - - describe('run()', () => { - it('should reply that a mention is required if no mention is passed.', () => { - messageMock.mentions.channels.first.mockReturnValueOnce(null); - command.run(messageMock); - - expect(messageMock.reply).toHaveBeenCalledTimes(1); - expect(messageMock.reply.mock.calls[0][0]).toContain('you need to mention the channel'); - }); - - it('should reply if the channel is not viewable.', () => { - channelMock.viewable = false; - command.run(messageMock); - - expect(messageMock.reply).toHaveBeenCalledTimes(1); - expect(messageMock.reply.mock.calls[0][0]).toContain('I cannot see the channel'); - - channelMock.viewable = true; - }); - }); - - describe('updateChannel()', () => { - beforeAll(() => { - clientMock.dataProvider.get.mockResolvedValue('old id'); - clientMock.dataProvider.set.mockResolvedValue(); - }); - - it('should reply if the channel previously set is the same as the new one.', () => { - clientMock.dataProvider.get.mockResolvedValueOnce(channelMock.id); - - return command.updateChannel(messageMock, channelMock) - .then(() => { - expect(messageMock.reply).toHaveBeenCalledTimes(1); - expect(messageMock.reply.mock.calls[0][0]).toContain('is already set'); - }); - }); - - it('should call client.dataProvider.set with the correct arguments.', () => { - return command.updateChannel(messageMock, channelMock) - .then(() => { - expect(clientMock.dataProvider.set).toHaveBeenCalledTimes(1); - expect(clientMock.dataProvider.set).toHaveBeenCalledWith(messageMock.guild, GUILD_KEYS.channel, channelMock.id); - }); - }); - - it('should log and reply if the channel has been updated.', () => { - return command.updateChannel(messageMock, channelMock) - .then(() => { - expect(logger.info).toHaveBeenCalledTimes(1); - expect(logger.info.mock.calls[0][0]).toContain('channel changed'); - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - expect(messageMock.channel.send.mock.calls[0][0]).toContain('channel has been change'); - }); - }); - - it('should log and reply if there was an error when setting the channel.', () => { - const expectedError = new Error('Oops'); - clientMock.dataProvider.set.mockRejectedValueOnce(expectedError); - - return command.updateChannel(messageMock, channelMock) - .then(() => { - expect(logger.error).toHaveBeenCalledTimes(1); - expect(logger.error).toHaveBeenCalledWith(expectedError); - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - expect(messageMock.channel.send.mock.calls[0][0]).toContain('Something happened'); - }); - }); - }); -}); diff --git a/test/commands/misc/HelpCommand.spec.js b/test/commands/misc/HelpCommand.spec.js deleted file mode 100644 index 4da908c..0000000 --- a/test/commands/misc/HelpCommand.spec.js +++ /dev/null @@ -1,65 +0,0 @@ -const logger = require('@greencoast/logger'); -const Discord = require('discord.js'); -const HelpCommand = require('../../../src/commands/misc/HelpCommand'); -const { clientMock, messageMock } = require('../../../__mocks__/discordMocks'); -const { MESSAGE_EMBED } = require('../../../src/common/constants'); - -let command; - -jest.mock('@greencoast/logger'); - -jest.mock('discord.js', () => { - const Discord = jest.requireActual('discord.js'); - - Discord.MessageEmbed.prototype.setTitle = jest.fn(function() { - return this; - }); - Discord.MessageEmbed.prototype.setColor = jest.fn(function() { - return this; - }); - Discord.MessageEmbed.prototype.setThumbnail = jest.fn(function() { - return this; - }); - Discord.MessageEmbed.prototype.addField = jest.fn(function() { - return this; - }); - - return Discord; -}); - -const embed = Discord.MessageEmbed.prototype; - -describe('Commands - Help', () => { - beforeEach(() => { - messageMock.channel.send.mockClear(); - logger.info.mockClear(); - - embed.setTitle.mockClear(); - embed.setColor.mockClear(); - embed.setThumbnail.mockClear(); - embed.addField.mockClear(); - - command = new HelpCommand(clientMock); - }); - - it('should create an embed with the correct information.', () => { - command.run(messageMock); - - expect(embed.setTitle).toHaveBeenCalledTimes(1); - expect(embed.setColor).toHaveBeenCalledTimes(1); - expect(embed.setThumbnail).toHaveBeenCalledTimes(1); - expect(embed.addField).toHaveBeenCalledTimes(clientMock.registry.groups.length + 1); - - expect(embed.setTitle).toHaveBeenCalledWith('Free Games Notifier Help Message'); - expect(embed.setColor).toHaveBeenCalledWith(MESSAGE_EMBED.color); - expect(embed.setThumbnail).toHaveBeenCalledWith(MESSAGE_EMBED.thumbnail); - - expect(embed.addField).toHaveBeenCalledWith('Found a bug?', `This bot is far from perfect, so in case you found a bug, please report it [here](${MESSAGE_EMBED.issuesURL}).`); - }); - - it('should send an embed.', () => { - command.run(messageMock); - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - expect(messageMock.channel.send.mock.calls[0][0]).toBeInstanceOf(Discord.MessageEmbed); - }); -}); diff --git a/test/commands/misc/OffersCommand.spec.js b/test/commands/misc/OffersCommand.spec.js deleted file mode 100644 index 1179036..0000000 --- a/test/commands/misc/OffersCommand.spec.js +++ /dev/null @@ -1,143 +0,0 @@ -const logger = require('@greencoast/logger'); -const OffersCommand = require('../../../src/commands/misc/OffersCommand'); -const ProviderFactory = require('../../../src/classes/providers/ProviderFactory'); -const { clientMock, messageMock } = require('../../../__mocks__/discordMocks'); -const { offerMock, providerMock } = require('../../../__mocks__/providers'); - -jest.mock('@greencoast/logger'); -jest.mock('../../../src/classes/providers/ProviderFactory'); - -let command; - -describe('Commands - OffersCommand', () => { - beforeAll(() => { - ProviderFactory.getAll.mockReturnValue([providerMock, providerMock]); - ProviderFactory.getInstance.mockImplementation((name) => { - if (name === 'valid') { - return providerMock; - } - throw new TypeError('Invalid provider'); - }); - }); - - beforeEach(() => { - logger.info.mockClear(); - messageMock.channel.send.mockClear(); - - command = new OffersCommand(clientMock); - }); - - describe('run()', () => { - it('should call handleAllProviders if no argument is passed.', () => { - const handleSpy = jest.spyOn(command, 'handleAllProviders').mockReturnValue(null); - - command.run(messageMock, []); - - expect(handleSpy).toHaveBeenCalledTimes(1); - expect(handleSpy).toHaveBeenCalledWith(messageMock); - }); - - it('should call handleAllProviders if no argument is passed.', () => { - const handleSpy = jest.spyOn(command, 'handleSingleProvider').mockReturnValue(null); - - command.run(messageMock, ['name']); - - expect(handleSpy).toHaveBeenCalledTimes(1); - expect(handleSpy).toHaveBeenCalledWith(messageMock, 'name'); - }); - }); - - describe('prepareMessageForOffer()', () => { - it('should return a string with the proper structure.', () => { - expect(command.prepareMessageForOffer('header', [offerMock])).toBe(`header\n\n1. ${offerMock.game} - available at: ${offerMock.url}\n`); - }); - }); - - describe('handleAllProviders()', () => { - beforeEach(() => { - providerMock.getOffers.mockResolvedValue([offerMock]); - }); - - it('should send a message if something happened when fetching the offers.', () => { - providerMock.getOffers.mockResolvedValue(null); - - return command.handleAllProviders(messageMock) - .then(() => { - expect(messageMock.channel.send).toHaveBeenCalledTimes(2); - expect(messageMock.channel.send.mock.calls[0][0]).toContain('Something happened'); - }); - }); - - it('should not do anything if no offers are available.', () => { - providerMock.getOffers.mockResolvedValue([]); - - return command.handleAllProviders(messageMock) - .then(() => { - expect(messageMock.channel.send).not.toHaveBeenCalled(); - }); - }); - - it('should send a message with the offer string.', () => { - jest.spyOn(command, 'prepareMessageForOffer').mockReturnValue('message'); - - return command.handleAllProviders(messageMock) - .then(() => { - expect(messageMock.channel.send).toHaveBeenCalledTimes(2); - expect(messageMock.channel.send).toHaveBeenCalledWith('message'); - }); - }); - }); - - describe('handleSingleProvider()', () => { - it('should send a message if the providerName is not valid.', () => { - return command.handleSingleProvider(messageMock, 'invalid') - .then(() => { - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - }); - }); - - it('should reject if something else happened.', () => { - const expectedError = new Error('Oops'); - ProviderFactory.getInstance.mockImplementationOnce(() => { - throw expectedError; - }); - - expect.assertions(1); - - return command.handleSingleProvider(messageMock, 'valid') - .catch((error) => { - expect(error).toBe(expectedError); - }); - }); - - it('should send a message if something happened when fetching the offers.', () => { - providerMock.getOffers.mockResolvedValueOnce(null); - - return command.handleSingleProvider(messageMock, 'valid') - .then(() => { - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - expect(messageMock.channel.send.mock.calls[0][0]).toContain('Something happened'); - }); - }); - - it('should send a message if no offers are currently available.', () => { - providerMock.getOffers.mockResolvedValueOnce([]); - - return command.handleSingleProvider(messageMock, 'valid') - .then(() => { - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - expect(messageMock.channel.send.mock.calls[0][0]).toContain('no free games'); - }); - }); - - it('should send a message with the offer string.', () => { - jest.spyOn(command, 'prepareMessageForOffer').mockReturnValue('message'); - - return command.handleSingleProvider(messageMock, 'valid') - .then(() => { - expect(messageMock.channel.send).toHaveBeenCalledTimes(1); - expect(messageMock.channel.send).toHaveBeenCalledWith('message'); - }); - }); - }); -}); diff --git a/test/common/context.spec.js b/test/common/context.spec.js deleted file mode 100644 index 6b1c441..0000000 --- a/test/common/context.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -let context; - -describe('Common - Context', () => { - describe('DEBUG_ENABLED', () => { - it('should return false if no --debug flag was used.', () => { - const oldArgs = [...process.argv]; - process.argv = ['npm', 'start']; - jest.resetModules(); - context = require('../../src/common/context'); - - expect(context.DEBUG_ENABLED).toBe(false); - - process.argv = oldArgs; - }); - - it('should return true if --debug flag was used.', () => { - const oldArgs = [...process.argv]; - process.argv = ['npm', 'start', '--debug']; - jest.resetModules(); - context = require('../../src/common/context'); - - expect(context.DEBUG_ENABLED).toBe(true); - - process.argv = oldArgs; - }); - }); - - describe('DEV_MODE', () => { - it('should return true if NODE_ENV is set to development.', () => { - process.env.NODE_ENV = 'development'; - jest.resetModules(); - context = require('../../src/common/context'); - - expect(context.DEV_MODE).toBe(true); - }); - - it('should return false if NODE_ENV is not set to development.', () => { - process.env.NODE_ENV = 'production'; - jest.resetModules(); - context = require('../../src/common/context'); - - expect(context.DEV_MODE).toBe(false); - }); - }); -}); diff --git a/test/setupEnv.js b/test/setupEnv.js deleted file mode 100644 index 3cde385..0000000 --- a/test/setupEnv.js +++ /dev/null @@ -1,3 +0,0 @@ -/* eslint-disable prefer-destructuring */ -/* eslint-disable no-global-assign */ -TextDecoder = require('text-encoding').TextDecoder; From a3cc97816bfa66dca8ce837291527d7fcac3358a Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Tue, 30 Jul 2024 11:42:23 -0500 Subject: [PATCH 02/52] Initialized project files. --- .eslintignore | 2 + .eslintrc | 6 + .gitignore | 134 + LICENSE | 21 + README.md | 1 + jest.config.js | 9 + package-lock.json | 10078 +++++++++++++++++++++++++++++++++++++++ package.json | 50 + src/app.ts | 0 src/config/app.spec.ts | 26 + src/config/app.ts | 3 + src/global.d.ts | 7 + tsconfig.base.json | 38 + tsconfig.build.json | 12 + tsconfig.dev.json | 17 + tsconfig.json | 3 + 16 files changed, 10407 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 jest.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/app.ts create mode 100644 src/config/app.spec.ts create mode 100644 src/config/app.ts create mode 100644 src/global.d.ts create mode 100644 tsconfig.base.json create mode 100644 tsconfig.build.json create mode 100644 tsconfig.dev.json create mode 100644 tsconfig.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..dd87e2d --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +build diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9956fa6 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "@moonstar-x/eslint-config/rules/node", + "@moonstar-x/eslint-config/rules/typescript" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55a15cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Build +build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..497a988 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 moonstar-x + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ba0e8c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Free Games Notifier for Discord diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..06f73e3 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + roots: ['<rootDir>'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + }, + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + testEnvironment: 'node' +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..db62fb3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10078 @@ +{ + "name": "discord-free-games-notifier", + "version": "3.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "discord-free-games-notifier", + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "@moonstar-x/logger": "^1.0.0", + "discord.js": "^14.15.3", + "dotenv": "^16.4.5", + "pg-promise": "^11.9.1", + "redis": "^4.7.0" + }, + "devDependencies": { + "@moonstar-x/eslint-config": "^1.0.0", + "@types/jest": "^29.5.12", + "@types/node": "^22.0.0", + "cross-env": "^7.0.3", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "nodemon": "^3.1.4", + "ts-jest": "^29.2.3", + "ts-node": "^10.9.2", + "typescript": "^5.5.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.1.tgz", + "integrity": "sha512-Y956ghgTT4j7rKesabkh5WeqgSFZVFwaPR0IWFm7KFHFmmJ4afbG49SmfW4S+GyRPx0Dy5jxEWA5t0rpxfElWg==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.0.tgz", + "integrity": "sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.0.tgz", + "integrity": "sha512-dG0aApncVQwAUJa8tP1VHTnmU67BeIQvKafd3raEx315H54FfkZSz3B/TT+33ZQAjatGJA79gZqTtqL5QZUKXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.7.tgz", + "integrity": "sha512-RL9GR0pUG5Kc8BUWLNDm2T5OpYwSX15r98I0IkgmRQTXuELq/OynH8xtMTMvTJFjXbMWFVTKtYkTaYQsuAwQlQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-decorators": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.7.tgz", + "integrity": "sha512-Ui4uLJJrRV1lb38zg1yYTmRKmiZLiftDEvZN2iq3kd9kUFU+PttmzTbAFC2ucRk/XJmtek6G23gPsuZbhrT8fQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", + "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz", + "integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.2.tgz", + "integrity": "sha512-InBZ0O8tew5V0K6cHcQ+wgxlrjOw1W4wDXLkOTjLRD8GYhTSkxTVBtdy3MMtvYBrbAWa1Qm3hNoTc1620Yj+Mg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-flow": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", + "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz", + "integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.2.tgz", + "integrity": "sha512-Y2Vkwy3ITW4id9c6KXshVV/x5yCGK7VdJmKkzOzNsDZMojRKfSA/033rRbLqlRozmhRXCejxWHLSJOg/wUHfzw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.0", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", + "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.24.7", + "@babel/plugin-transform-react-jsx-development": "^7.24.7", + "@babel/plugin-transform-react-pure-annotations": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.2.tgz", + "integrity": "sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.8.2.tgz", + "integrity": "sha512-6wvG3QaCjtMu0xnle4SoOIeFB4y6fKMN6WZfy3BMKJdQQtPLik8KGzDwBVL/+wTtcE/ZlFjgEk74GublyEVZ7g==", + "dependencies": { + "@discordjs/formatters": "^0.4.0", + "@discordjs/util": "^1.1.0", + "@sapphire/shapeshift": "^3.9.7", + "discord-api-types": "0.37.83", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.4.0.tgz", + "integrity": "sha512-fJ06TLC1NiruF35470q3Nr1bi95BdvKFAF+T5bNfZJ4bNdqZ3VZ+Ttg6SThqTxm6qumSG3choxLBHMC69WXNXQ==", + "dependencies": { + "discord-api-types": "0.37.83" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.3.0.tgz", + "integrity": "sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "0.37.83", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.2", + "undici": "6.13.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz", + "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.0.tgz", + "integrity": "sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==", + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz", + "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.3.0", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "0.37.83", + "tslib": "^2.6.2", + "ws": "^8.16.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz", + "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@moonstar-x/eslint-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@moonstar-x/eslint-config/-/eslint-config-1.0.0.tgz", + "integrity": "sha512-DvRFBTvYZV0CH2YwF8O1HJg3mqwYgvXzZrMh/UyBUHcnY6VV+iCW5+Scqmgd12k7iDxHJ54uBnhlf2nzIjRbGQ==", + "dev": true, + "dependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@typescript-eslint/parser": "^7.17.0", + "eslint-config-react-app": "^7.0.1" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": ">=7", + "eslint": ">=8" + } + }, + "node_modules/@moonstar-x/logger": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@moonstar-x/logger/-/logger-1.0.0.tgz", + "integrity": "sha512-kOkjVSW6Jx6COBK00x/9kT1g1E3I++sNDxU8a3aTZy1WYEh6hXTQutv34Z2+Rxrkd7FFJNn43qdhbnSkh/3sHw==", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "dev": true + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.3.tgz", + "integrity": "sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz", + "integrity": "sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", + "dependencies": { + "undici-types": "~6.11.1" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.4.tgz", + "integrity": "sha512-ZL62PFXEIeGUI8btfJ5S8Flc286eU1ZUSjwyFQtIGXfRUDPZKO+CDJMYb1R71LjGWRZ4n202O+a6FGjsgTw58g==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assert-options": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.1.tgz", + "integrity": "sha512-5lNGRB5g5i2bGIzb+J1QQE1iKU/WEMVBReFIc5pPDWjcPj23otPL0eI6PB2v7QPi0qU6Mhym5D3y0ZiSIOf3GA==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", + "integrity": "sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001644", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001644.tgz", + "integrity": "sha512-YGvlOZB4QhZuiis+ETS0VXR+MExbFf4fZYYeMTEE0aTQd/RdIjkTyZjLrbYVKnHzppDvnOhritRVv+i7Go6mHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, + "node_modules/discord.js": { + "version": "14.15.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.15.3.tgz", + "integrity": "sha512-/UJDQO10VuU6wQPglA4kz2bw2ngeeSbogiIPx/TsnctfzV/tNf+q+i1HlgtX1OGpeOBpJH9erZQNO5oRM2uAtQ==", + "dependencies": { + "@discordjs/builders": "^1.8.2", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.4.0", + "@discordjs/rest": "^2.3.0", + "@discordjs/util": "^1.1.0", + "@discordjs/ws": "^1.1.1", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "0.37.83", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "2.6.2", + "undici": "6.13.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.3.tgz", + "integrity": "sha512-QNdYSS5i8D9axWp/6XIezRObRHqaav/ur9z1VzCDUCH1XIFOr9WQk5xmgunhsTpjjgDy3oLxO/WMOVZlpUQrlA==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-react-app/node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", + "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "dev": true, + "dependencies": { + "aria-query": "~5.1.3", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.9.1", + "axobject-query": "~3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.19", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-bytes.js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pg": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", + "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "dependencies": { + "pg-connection-string": "^2.6.4", + "pg-pool": "^3.6.2", + "pg-protocol": "^1.6.1", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", + "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-minify": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.5.tgz", + "integrity": "sha512-u0UE8veaCnMfJmoklqneeBBopOAPG3/6DHqGVHYAhz8DkJXh9dnjPlz25fRxn4e+6XVzdOp7kau63Rp52fZ3WQ==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", + "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-promise": { + "version": "11.9.1", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.9.1.tgz", + "integrity": "sha512-qvMmyDvWd64X0a25hCuWV40GLMbgeYf4z7ZmzxQqGHtUIlzMtxcMtaBHAMr7XVOL62wFv2ZVKW5pFruD/4ZAOg==", + "dependencies": { + "assert-options": "0.8.1", + "pg": "8.12.0", + "pg-minify": "1.6.5", + "spex": "3.3.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", + "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spex": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/spex/-/spex-3.3.0.tgz", + "integrity": "sha512-VNiXjFp6R4ldPbVRYbpxlD35yRHceecVXlct1J4/X80KuuPnW2AXMq3sGwhnJOhKkUsOxAT6nRGfGE5pocVw5w==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string.prototype.includes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", + "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-jest": { + "version": "29.2.3", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.3.tgz", + "integrity": "sha512-yCcfVdiBFngVz9/keHin9EnsrQtQtEu3nRykNy9RVp+FiPFFbPJ3Sg6Qg4+TkmH0vMP5qsTKgXSsk80HRwvdgQ==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/undici": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.13.0.tgz", + "integrity": "sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/undici-types": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a60012d --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "discord-free-games-notifier", + "version": "3.0.0", + "description": "A Discord bot that will notify you when games on various storefronts become free.", + "private": true, + "main": "./build/app.js", + "scripts": { + "dev": "cross-env NODE_ENV=development ts-node ./src/app.ts", + "dev:watch": "cross-env NODE_ENV=development nodemon ./src/app.ts", + "build:prod": "tsc -p ./tsconfig.build.json", + "build:full": "tsc -p ./tsconfig.json", + "type-check": "tsc --noEmit", + "lint": "eslint . --ext .tsx,.ts,.js", + "lint:fix": "eslint . --ext .tsx,.ts,.js --fix", + "test": "jest", + "test:watch": "jest --watch" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/moonstar-x/discord-free-games-notifier.git" + }, + "homepage": "https://docs.moonstar-x.dev/discord-free-games-notifier", + "bugs": { + "url": "https://github.com/moonstar-x/discord-free-games-notifier/issues" + }, + "author": "moonstar-x", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "dependencies": { + "@moonstar-x/logger": "^1.0.0", + "discord.js": "^14.15.3", + "dotenv": "^16.4.5", + "pg-promise": "^11.9.1", + "redis": "^4.7.0" + }, + "devDependencies": { + "@moonstar-x/eslint-config": "^1.0.0", + "@types/jest": "^29.5.12", + "@types/node": "^22.0.0", + "cross-env": "^7.0.3", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "nodemon": "^3.1.4", + "ts-jest": "^29.2.3", + "ts-node": "^10.9.2", + "typescript": "^5.5.4" + } +} diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/config/app.spec.ts b/src/config/app.spec.ts new file mode 100644 index 0000000..b936bfd --- /dev/null +++ b/src/config/app.spec.ts @@ -0,0 +1,26 @@ +import * as configModule from './app'; + +describe('Config > App', () => { + let config: typeof configModule; + + const oldEnv = { ...process.env }; + const mockedEnv = { + DISCORD_TOKEN: 'token' + }; + + beforeAll(() => { + process.env = mockedEnv; + jest.resetModules(); + config = require('./app'); + }); + + afterAll(() => { + process.env = oldEnv; + jest.resetModules(); + config = require('./app'); + }); + + it('should export valid DISCORD_TOKEN.', () => { + expect(config.DISCORD_TOKEN).toBe('token'); + }); +}); diff --git a/src/config/app.ts b/src/config/app.ts new file mode 100644 index 0000000..7a44a74 --- /dev/null +++ b/src/config/app.ts @@ -0,0 +1,3 @@ +import 'dotenv/config'; + +export const DISCORD_TOKEN = process.env.DISCORD_TOKEN!; diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..78fce6d --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,7 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + DISCORD_TOKE?: string + } + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..0f430de --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,38 @@ +{ + "compileOnSave": true, + + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + + "allowJs": false, + "declaration": false, + "declarationMap": false, + "outDir": "build", + "resolveJsonModule": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "useUnknownInCatchVariables": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + + "sourceMap": true, + "emitDeclarationOnly": false, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..be0f13e --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "include": ["src/**/*"], + "exclude": [ + "build", + "node_modules", + "src/**/*.spec.*", + "__mocks__", + "src/**/*/__mocks__" + ], + + "extends": "./tsconfig.base.json" +} diff --git a/tsconfig.dev.json b/tsconfig.dev.json new file mode 100644 index 0000000..d662828 --- /dev/null +++ b/tsconfig.dev.json @@ -0,0 +1,17 @@ +{ + "include": ["src/**/*", "__mocks__/**/*"], + "exclude": [ + "build", + "node_modules", + ], + + "extends": "./tsconfig.base.json", + + "files": [ + "src/app.ts", + "src/global.d.ts" + ], + "ts-node": { + "files": true + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cc6ea06 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.dev.json" +} From 6255bd7aaa181fa0229b6677b3d98d0b4fa1688c Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Wed, 31 Jul 2024 01:11:47 -0500 Subject: [PATCH 03/52] Created base for client and command handling. --- .eslintrc | 5 +- package-lock.json | 74 +++++++++++++++++++++++--- package.json | 22 +++++--- src/app.ts | 0 src/app/client.ts | 29 ++++++++++ src/app/deploy.ts | 13 +++++ src/base/client/ClientEventHandlers.ts | 51 ++++++++++++++++++ src/base/client/ExtendedClient.ts | 54 +++++++++++++++++++ src/base/command/Command.ts | 46 ++++++++++++++++ src/base/command/CommandDeployer.ts | 37 +++++++++++++ src/base/command/CommandDispatcher.ts | 39 ++++++++++++++ src/base/command/CommandRegistry.ts | 73 +++++++++++++++++++++++++ src/base/command/validators.ts | 30 +++++++++++ src/base/error/command.ts | 7 +++ src/base/types/guards.ts | 5 ++ src/commands/test/TestCommand.ts | 17 ++++++ src/config/context.ts | 1 + src/entrypoint/deploy.ts | 20 +++++++ src/entrypoint/start.ts | 6 +++ src/entrypoint/startSharded.ts | 15 ++++++ src/utils/types.ts | 1 + tsconfig.dev.json | 2 +- 22 files changed, 529 insertions(+), 18 deletions(-) delete mode 100644 src/app.ts create mode 100644 src/app/client.ts create mode 100644 src/app/deploy.ts create mode 100644 src/base/client/ClientEventHandlers.ts create mode 100644 src/base/client/ExtendedClient.ts create mode 100644 src/base/command/Command.ts create mode 100644 src/base/command/CommandDeployer.ts create mode 100644 src/base/command/CommandDispatcher.ts create mode 100644 src/base/command/CommandRegistry.ts create mode 100644 src/base/command/validators.ts create mode 100644 src/base/error/command.ts create mode 100644 src/base/types/guards.ts create mode 100644 src/commands/test/TestCommand.ts create mode 100644 src/config/context.ts create mode 100644 src/entrypoint/deploy.ts create mode 100644 src/entrypoint/start.ts create mode 100644 src/entrypoint/startSharded.ts create mode 100644 src/utils/types.ts diff --git a/.eslintrc b/.eslintrc index 9956fa6..392f623 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,5 +2,8 @@ "extends": [ "@moonstar-x/eslint-config/rules/node", "@moonstar-x/eslint-config/rules/typescript" - ] + ], + "rules": { + "no-dupe-class-members": "off" + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index db62fb3..d4328dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,21 @@ "version": "3.0.0", "license": "MIT", "dependencies": { - "@moonstar-x/logger": "^1.0.0", + "@moonstar-x/logger": "^1.0.1", + "discord-api-types": "^0.37.93", "discord.js": "^14.15.3", "dotenv": "^16.4.5", + "flat": "^5.0.2", "pg-promise": "^11.9.1", - "redis": "^4.7.0" + "redis": "^4.7.0", + "require-all": "^3.0.0" }, "devDependencies": { "@moonstar-x/eslint-config": "^1.0.0", + "@types/flat": "^5.0.5", "@types/jest": "^29.5.12", "@types/node": "^22.0.0", + "@types/require-all": "^3.0.6", "cross-env": "^7.0.3", "eslint": "^8.57.0", "jest": "^29.7.0", @@ -2215,6 +2220,11 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/builders/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, "node_modules/@discordjs/collection": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", @@ -2237,6 +2247,11 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/formatters/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, "node_modules/@discordjs/rest": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.3.0.tgz", @@ -2270,6 +2285,11 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/rest/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, "node_modules/@discordjs/util": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.0.tgz", @@ -2314,6 +2334,11 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@discordjs/ws/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2855,9 +2880,9 @@ } }, "node_modules/@moonstar-x/logger": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@moonstar-x/logger/-/logger-1.0.0.tgz", - "integrity": "sha512-kOkjVSW6Jx6COBK00x/9kT1g1E3I++sNDxU8a3aTZy1WYEh6hXTQutv34Z2+Rxrkd7FFJNn43qdhbnSkh/3sHw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@moonstar-x/logger/-/logger-1.0.1.tgz", + "integrity": "sha512-VobsTGETcGMIMP0bsSemyy0nQNbcmUcn4XPgNI2joKr+fLUvYSTOZ4kxKthMQHX37l7hNhdrZzKJhKnIj7PYAA==", "dependencies": { "chalk": "^4.1.2" }, @@ -3114,6 +3139,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/flat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.5.tgz", + "integrity": "sha512-nPLljZQKSnac53KDUDzuzdRfGI0TDb5qPrb+SrQyN3MtdQrOnGsKniHN1iYZsJEBIVQve94Y6gNz22sgISZq+Q==", + "dev": true + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -3183,6 +3214,12 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "dev": true }, + "node_modules/@types/require-all": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/require-all/-/require-all-3.0.6.tgz", + "integrity": "sha512-93iiG8N8kovFL08oenE76F7adIE00uzTdGV7FlFE9uPDtZ2f3pSuOS0ICWjG9OMhsn38eJjhf5CKFp9s7fzz/A==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -4752,9 +4789,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.37.83", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", - "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + "version": "0.37.93", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.93.tgz", + "integrity": "sha512-M5jn0x3bcXk8EI2c6F6V6LeOWq10B/cJf+YJSyqNmg7z4bdXK+Z7g9zGJwHS0h9Bfgs0nun2LQISFOzwck7G9A==" }, "node_modules/discord.js": { "version": "14.15.3", @@ -4781,6 +4818,11 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/discord.js/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -6030,6 +6072,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -8801,6 +8851,14 @@ "jsesc": "bin/jsesc" } }, + "node_modules/require-all": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", + "integrity": "sha512-jPGN876lc5exWYrMcgZSd7U42P0PmVQzxnQB13fCSzmyGnqQWW4WUz5DosZ/qe24hz+5o9lSvW2epBNZ1xa6Fw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index a60012d..6a93e6b 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,18 @@ "version": "3.0.0", "description": "A Discord bot that will notify you when games on various storefronts become free.", "private": true, - "main": "./build/app.js", "scripts": { - "dev": "cross-env NODE_ENV=development ts-node ./src/app.ts", - "dev:watch": "cross-env NODE_ENV=development nodemon ./src/app.ts", - "build:prod": "tsc -p ./tsconfig.build.json", - "build:full": "tsc -p ./tsconfig.json", + "dev": "cross-env NODE_ENV=development nodemon src/entrypoint/start.ts", + "build": "tsc -p ./tsconfig.build.json", "type-check": "tsc --noEmit", "lint": "eslint . --ext .tsx,.ts,.js", "lint:fix": "eslint . --ext .tsx,.ts,.js --fix", "test": "jest", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "deploy:dev": "ts-node src/entrypoint/deploy.ts", + "deploy:prod": "node build/entrypoint/deploy.ts", + "start": "node build/entrypoint/start.js", + "start:sharded": "node build/entrypoint/startSharded.js" }, "repository": { "type": "git", @@ -29,16 +30,21 @@ "node": ">=20" }, "dependencies": { - "@moonstar-x/logger": "^1.0.0", + "@moonstar-x/logger": "^1.0.1", + "discord-api-types": "^0.37.93", "discord.js": "^14.15.3", "dotenv": "^16.4.5", + "flat": "^5.0.2", "pg-promise": "^11.9.1", - "redis": "^4.7.0" + "redis": "^4.7.0", + "require-all": "^3.0.0" }, "devDependencies": { "@moonstar-x/eslint-config": "^1.0.0", + "@types/flat": "^5.0.5", "@types/jest": "^29.5.12", "@types/node": "^22.0.0", + "@types/require-all": "^3.0.6", "cross-env": "^7.0.3", "eslint": "^8.57.0", "jest": "^29.7.0", diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/client.ts b/src/app/client.ts new file mode 100644 index 0000000..3e322b0 --- /dev/null +++ b/src/app/client.ts @@ -0,0 +1,29 @@ +import path from 'path'; +import { GatewayIntentBits } from 'discord.js'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import * as ClientEventHandlers from '../base/client/ClientEventHandlers'; +import { DEBUG_ENABLED } from '../config/context'; + +export const createClient = () => { + const client = new ExtendedClient({ + intents: [GatewayIntentBits.Guilds] as const + }); + + client.registry.registerIn(path.join(__dirname, '../commands')); + + if (DEBUG_ENABLED) { + client.on('debug', ClientEventHandlers.handleDebug); + } + + client.on('error', ClientEventHandlers.handleError); + client.on('guildCreate', ClientEventHandlers.handleGuildCreate); + client.on('guildDelete', ClientEventHandlers.handleGuildDelete); + client.on('guildUnavailable', ClientEventHandlers.handleGuildUnavailable); + client.on('ready', ClientEventHandlers.handleReady); + client.on('warn', ClientEventHandlers.handleWarn); + client.on('commandExecute', ClientEventHandlers.handleCommandExecute); + client.on('commandError', ClientEventHandlers.handleCommandError); + client.on('commandRegistered', ClientEventHandlers.handleCommandRegistered); + + return client; +}; diff --git a/src/app/deploy.ts b/src/app/deploy.ts new file mode 100644 index 0000000..57fa7dd --- /dev/null +++ b/src/app/deploy.ts @@ -0,0 +1,13 @@ +import path from 'path'; +import { IntentsBitField } from 'discord.js'; +import { ExtendedClient } from '../base/client/ExtendedClient'; + +export const createDeployClient = () => { + const client = new ExtendedClient({ + intents: [IntentsBitField.Flags.Guilds] as const + }); + + client.registry.registerIn(path.join(__dirname, '../commands')); + + return client; +}; diff --git a/src/base/client/ClientEventHandlers.ts b/src/base/client/ClientEventHandlers.ts new file mode 100644 index 0000000..45b5f7b --- /dev/null +++ b/src/base/client/ClientEventHandlers.ts @@ -0,0 +1,51 @@ +import { ChatInputCommandInteraction, Guild, GuildMember } from 'discord.js'; +import logger from '@moonstar-x/logger'; +import { Command } from '../command/Command'; + +export const handleDebug = (info: string) => { + logger.debug(info); +}; + +export const handleError = (error: Error) => { + logger.error(error); +}; + +export const handleGuildCreate = (guild: Guild) => { + logger.info(`Joined guild ${guild.name}`); +}; + +// TODO: Delete guild data in database. +export const handleGuildDelete = (guild: Guild) => { + logger.info(`Left guild ${guild.name}`); +}; + +export const handleGuildUnavailable = (guild: Guild) => { + logger.warn(`Guild ${guild.name} is unavailable.`); +}; + +export const handleReady = () => { + logger.info('Ready!'); +}; + +export const handleWarn = (info: string) => { + logger.warn(info); +}; + +export const handleCommandExecute = (command: Command, interaction: ChatInputCommandInteraction) => { + const author = interaction.member instanceof GuildMember ? interaction.member.displayName : interaction.user.displayName; + const guild = interaction.guild?.name || 'Unknown Guild or DM'; + + logger.info(`User ${author} issued command ${command.name} in ${guild}`); +}; + +export const handleCommandError = (error: unknown, command: Command, interaction: ChatInputCommandInteraction) => { + const guild = interaction.guild?.name || 'Unknown Guild or DM'; + + logger.error(`Something happened when executing ${command.name} in ${guild}.`); + logger.error(`Command interaction has ID of ${interaction.id}`); + logger.error(error); +}; + +export const handleCommandRegistered = (command: Command) => { + logger.info(`Registered command: ${command.name}`); +}; diff --git a/src/base/client/ExtendedClient.ts b/src/base/client/ExtendedClient.ts new file mode 100644 index 0000000..082dad1 --- /dev/null +++ b/src/base/client/ExtendedClient.ts @@ -0,0 +1,54 @@ +import { Client, ClientOptions, ClientEvents, ChatInputCommandInteraction } from 'discord.js'; +import { CommandRegistry } from '../command/CommandRegistry'; +import { CommandDispatcher } from '../command/CommandDispatcher'; +import { Command } from '../command/Command'; + +export interface ExtendedClientEvents extends ClientEvents { + commandExecute: [command: Command, interaction: ChatInputCommandInteraction] + commandError: [error: unknown, command: Command, interaction: ChatInputCommandInteraction] + commandRegistered: [command: Command] +} + +export declare interface ExtendedClient { + on<K extends keyof ExtendedClientEvents>(event: K, listener: (...args: ExtendedClientEvents[K]) => void): this; + on<S extends string | symbol>( + event: Exclude<S, keyof ExtendedClientEvents>, + listener: (...args: unknown[]) => void + ): this; + + once<K extends keyof ExtendedClientEvents>(event: K, listener: (...args: ExtendedClientEvents[K]) => void): this; + once<S extends string | symbol>( + event: Exclude<S, keyof ExtendedClientEvents>, + listener: (...args: unknown[]) => void + ): this; + + emit<K extends keyof ExtendedClientEvents>(event: K, ...args: ExtendedClientEvents[K]): boolean; + emit<S extends string | symbol>(event: Exclude<S, keyof ExtendedClientEvents>, ...args: unknown[]): boolean; + + off<K extends keyof ExtendedClientEvents>(event: K, listener: (...args: ExtendedClientEvents[K]) => void): this; + off<S extends string | symbol>( + event: Exclude<S, keyof ExtendedClientEvents>, + listener: (...args: unknown[]) => void + ): this; + + removeAllListeners<K extends keyof ExtendedClientEvents>(event?: K): this; + removeAllListeners<S extends string | symbol>(event?: Exclude<S, keyof ExtendedClientEvents>): this; +} + +export class ExtendedClient extends Client { + public readonly registry: CommandRegistry; + public readonly dispatcher: CommandDispatcher; + + public constructor(options: ClientOptions) { + super(options); + + this.registry = new CommandRegistry(this); + this.dispatcher = new CommandDispatcher(this, this.registry); + + this.registerBasicHandlers(); + } + + private registerBasicHandlers(): void { + this.on('interactionCreate', (interaction) => this.dispatcher.handleInteraction(interaction)); + } +} diff --git a/src/base/command/Command.ts b/src/base/command/Command.ts new file mode 100644 index 0000000..800c328 --- /dev/null +++ b/src/base/command/Command.ts @@ -0,0 +1,46 @@ +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { ExtendedClient } from '../client/ExtendedClient'; + +// TODO: Implement guildOnly +// TODO: Implement permissions. +export interface CommandOptions { + name: string + description: string + emoji?: string + builder: SlashCommandBuilder +} + +export abstract class Command { + public readonly client: ExtendedClient; + + public readonly name: string; + public readonly description: string; + public readonly emoji: string; + public readonly builder: SlashCommandBuilder; + + protected constructor(client: ExtendedClient, options: CommandOptions) { + this.client = client; + + this.name = options.name; + this.description = options.description; + this.emoji = options.emoji || ':robot:'; + this.builder = options.builder + .setName(this.name) + .setDescription(this.description); + } + + public abstract run(interaction: ChatInputCommandInteraction): Promise<void> | void; + + public async onError(error: unknown, interaction: ChatInputCommandInteraction): Promise<void> { + this.client.emit('commandError', error, this, interaction); + + const message = `An error has occurred when running the command ${this.name}`; + + if (interaction.deferred || interaction.replied) { + await interaction.editReply({ content: message }); + return; + } + + await interaction.reply({ content: message }); + } +} diff --git a/src/base/command/CommandDeployer.ts b/src/base/command/CommandDeployer.ts new file mode 100644 index 0000000..b8ff2bb --- /dev/null +++ b/src/base/command/CommandDeployer.ts @@ -0,0 +1,37 @@ +import { REST, User } from 'discord.js'; +import { Routes } from 'discord-api-types/v10'; +import { CommandRegistry } from './CommandRegistry'; +import { Command } from './Command'; + +export class CommandDeployer { + private readonly rest: REST; + + public constructor(token: string) { + this.rest = new REST({ version: '10' }).setToken(token); + } + + public async deployGlobally(registry: CommandRegistry): Promise<Command[]> { + const clientId = await this.getClientId(); + const commands = registry.getAll(); + const builders = commands.map((command) => command.builder); + + await this.rest.put(Routes.applicationCommands(clientId), { body: builders }); + + return commands; + } + + public async deployToGuild(registry: CommandRegistry, guildId: string): Promise<Command[]> { + const clientId = await this.getClientId(); + const commands = registry.getAll(); + const builders = commands.map((command) => command.builder); + + await this.rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: builders }); + + return commands; + } + + private async getClientId(): Promise<string> { + const response = await this.rest.get(Routes.user()) as User; + return response.id; + } +} diff --git a/src/base/command/CommandDispatcher.ts b/src/base/command/CommandDispatcher.ts new file mode 100644 index 0000000..8823458 --- /dev/null +++ b/src/base/command/CommandDispatcher.ts @@ -0,0 +1,39 @@ +import { BaseInteraction } from 'discord.js'; +import logger from '@moonstar-x/logger'; +import { ExtendedClient } from '../client/ExtendedClient'; +import { CommandRegistry } from './CommandRegistry'; +import { isChatInputCommand } from '../types/guards'; + + +export class CommandDispatcher { + public readonly client: ExtendedClient; + public readonly registry: CommandRegistry; + + public constructor(client: ExtendedClient, registry: CommandRegistry) { + this.client = client; + this.registry = registry; + } + + public async handleInteraction(interaction: BaseInteraction): Promise<void> { + if (!isChatInputCommand(interaction)) { + return; + } + + const command = this.registry.get(interaction.commandName); + if (!command) { + return; + } + + try { + this.client.emit('commandExecute', command, interaction); + await command.run(interaction); + } catch (error) { + try { + await command.onError(error, interaction); + } catch (handleError) { + logger.error(`There was an error in the command execution for ${command.name}.`, error); + logger.error(`Additionally, the error handling could not complete.`, handleError); + } + } + } +} diff --git a/src/base/command/CommandRegistry.ts b/src/base/command/CommandRegistry.ts new file mode 100644 index 0000000..77e00d8 --- /dev/null +++ b/src/base/command/CommandRegistry.ts @@ -0,0 +1,73 @@ +import { Collection } from 'discord.js'; +import requireAll from 'require-all'; +import { flatten } from 'flat'; +import { ExtendedClient } from '../client/ExtendedClient'; +import { Command } from './Command'; +import { validateCommand } from './validators'; +import { CommandRegistryError } from '../error/command'; +import { Class } from '../../utils/types'; + +export class CommandRegistry { + public readonly client: ExtendedClient; + public readonly commands: Collection<string, Command>; + + public constructor(client: ExtendedClient) { + this.client = client; + this.commands = new Collection(); + } + + public get(name: string): Command | undefined { + return this.commands.get(name); + } + + public getAll(): Command[] { + return Array.from(this.commands.values()); + } + + public size(): number { + return this.commands.size; + } + + public register(command: Command): this; + public register(commands: Command[]): this; + public register(command: Command | Command[]): this { + if (Array.isArray(command)) { + this.registerCommands(command); + } else { + this.registerCommand(command); + } + + return this; + } + + public registerIn(path: string): this { + const commandModules: Record<string, Class<Command>> = flatten( + requireAll({ + dirname: path, + filter: /^(?!.*spec\.(?:ts|js)$).*\.(?:ts|js)$/i, + recursive: true + }) + ); + const commands = Object.values(commandModules).map((Command) => new Command(this.client)); + + this.registerCommands(Object.values(commands)); + return this; + } + + private registerCommand(command: Command): void { + validateCommand(command); + + if (this.get(command.name)) { + throw new CommandRegistryError(`Command ${command.name} is already registered. Command names must be unique.`); + } + + this.commands.set(command.name, command); + this.client.emit('commandRegistered', command); + } + + private registerCommands(commands: Command[]): void { + for (const command of commands) { + this.registerCommand(command); + } + } +} diff --git a/src/base/command/validators.ts b/src/base/command/validators.ts new file mode 100644 index 0000000..95c6f1d --- /dev/null +++ b/src/base/command/validators.ts @@ -0,0 +1,30 @@ +import { Command } from './Command'; +import { CommandValidationError } from '../error/command'; + +const NAME_PATTERN = /^[\P{Lu}\p{N}_-]+$/u; +const NAME_MIN = 1; +const NAME_MAX = 32; + +const DESCRIPTION_MIN = 1; +const DESCRIPTION_MAX = 100; + +const validateName = (name: string) => { + if (name.length < NAME_MIN || name.length > NAME_MAX) { + throw new CommandValidationError(`${name} is not a valid command name. It must be between ${NAME_MIN} and ${NAME_MAX} characters.`); + } + + if (!NAME_PATTERN.test(name)) { + throw new CommandValidationError(`${name} is not a valid command name. Please use all lower-cased characters.`); + } +}; + +const validateDescription = (name: string, description: string) => { + if (description.length < DESCRIPTION_MIN || description.length > DESCRIPTION_MAX) { + throw new CommandValidationError(`${name} does not have a valid description. It must be between ${DESCRIPTION_MIN} and ${DESCRIPTION_MAX} characters.`); + } +}; + +export const validateCommand = (command: Command) => { + validateName(command.name); + validateDescription(command.name, command.description); +}; diff --git a/src/base/error/command.ts b/src/base/error/command.ts new file mode 100644 index 0000000..f929d07 --- /dev/null +++ b/src/base/error/command.ts @@ -0,0 +1,7 @@ +export class CommandRegistryError extends Error { + +} + +export class CommandValidationError extends Error { + +} diff --git a/src/base/types/guards.ts b/src/base/types/guards.ts new file mode 100644 index 0000000..233a014 --- /dev/null +++ b/src/base/types/guards.ts @@ -0,0 +1,5 @@ +import { BaseInteraction, ChatInputCommandInteraction } from 'discord.js'; + +export const isChatInputCommand = (interaction: BaseInteraction): interaction is ChatInputCommandInteraction => { + return interaction.isChatInputCommand(); +}; diff --git a/src/commands/test/TestCommand.ts b/src/commands/test/TestCommand.ts new file mode 100644 index 0000000..4d139f3 --- /dev/null +++ b/src/commands/test/TestCommand.ts @@ -0,0 +1,17 @@ +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { Command } from '../../base/command/Command'; +import { ExtendedClient } from '../../base/client/ExtendedClient'; + +export default class TestCommand extends Command { + public constructor(client: ExtendedClient) { + super(client, { + name: 'test', + description: 'Test', + builder: new SlashCommandBuilder() + }); + } + + public override async run(interaction: ChatInputCommandInteraction): Promise<void> { + await interaction.reply({ content: 'Hi!' }); + } +} diff --git a/src/config/context.ts b/src/config/context.ts new file mode 100644 index 0000000..baff6e8 --- /dev/null +++ b/src/config/context.ts @@ -0,0 +1 @@ +export const DEBUG_ENABLED = process.argv.includes('--debug'); diff --git a/src/entrypoint/deploy.ts b/src/entrypoint/deploy.ts new file mode 100644 index 0000000..0eea1ab --- /dev/null +++ b/src/entrypoint/deploy.ts @@ -0,0 +1,20 @@ +import logger from '@moonstar-x/logger'; +import { createDeployClient } from '../app/deploy'; +import { DISCORD_TOKEN } from '../config/app'; +import { CommandDeployer } from '../base/command/CommandDeployer'; + +const client = createDeployClient(); +const deployer = new CommandDeployer(DISCORD_TOKEN); + +deployer.deployGlobally(client.registry) + .then((commands) => { + commands.forEach((command) => { + logger.info(`Successfully deployed ${command.name} command globally.`); + }); + + logger.info(`Finished deploying ${commands.length} commands globally. These changes can take up to an hour to be reflected on Discord.`); + }) + .catch((error) => { + logger.error('Could not deploy commands globally.'); + logger.error(error); + }); diff --git a/src/entrypoint/start.ts b/src/entrypoint/start.ts new file mode 100644 index 0000000..6043b25 --- /dev/null +++ b/src/entrypoint/start.ts @@ -0,0 +1,6 @@ +import { createClient } from '../app/client'; +import { DISCORD_TOKEN } from '../config/app'; + +const client = createClient(); + +client.login(DISCORD_TOKEN); diff --git a/src/entrypoint/startSharded.ts b/src/entrypoint/startSharded.ts new file mode 100644 index 0000000..a472ac3 --- /dev/null +++ b/src/entrypoint/startSharded.ts @@ -0,0 +1,15 @@ +import path from 'path'; +import { ShardingManager } from 'discord.js'; +import logger from '@moonstar-x/logger'; +import { DISCORD_TOKEN } from '../config/app'; + +// Note: This works only when built, not in TypeScript. +const startScript = path.join(__dirname, `./start.js`); + +const manager = new ShardingManager(startScript, { token: DISCORD_TOKEN }); + +manager.on('shardCreate', (shard) => { + logger.info(`Launched shard with ID: ${shard.id}`); +}); + +manager.spawn({ amount: 'auto' }); diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..f6ff6f1 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1 @@ +export type Class<T> = new(...args: unknown[]) => T; diff --git a/tsconfig.dev.json b/tsconfig.dev.json index d662828..4cb1693 100644 --- a/tsconfig.dev.json +++ b/tsconfig.dev.json @@ -8,7 +8,7 @@ "extends": "./tsconfig.base.json", "files": [ - "src/app.ts", + "src/entrypoint/start.ts", "src/global.d.ts" ], "ts-node": { From c99bbb260e03a806ea9c954feaad574eaf6676df Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Wed, 31 Jul 2024 23:20:19 -0500 Subject: [PATCH 04/52] Added permissions and guildOnly for command structure. --- src/base/command/Command.ts | 31 ++++++++++++++++++++++++--- src/base/command/CommandDispatcher.ts | 11 ++++++++++ src/commands/test/TestCommand.ts | 4 +++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/base/command/Command.ts b/src/base/command/Command.ts index 800c328..c8d1ae7 100644 --- a/src/base/command/Command.ts +++ b/src/base/command/Command.ts @@ -1,12 +1,12 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, SlashCommandBuilder, PermissionResolvable, DMChannel, PermissionsBitField } from 'discord.js'; import { ExtendedClient } from '../client/ExtendedClient'; -// TODO: Implement guildOnly -// TODO: Implement permissions. export interface CommandOptions { name: string description: string emoji?: string + guildOnly: boolean + permissions?: PermissionResolvable | null builder: SlashCommandBuilder } @@ -16,6 +16,8 @@ export abstract class Command { public readonly name: string; public readonly description: string; public readonly emoji: string; + public readonly guildOnly: boolean; + public readonly permissions: PermissionResolvable | null; public readonly builder: SlashCommandBuilder; protected constructor(client: ExtendedClient, options: CommandOptions) { @@ -24,6 +26,8 @@ export abstract class Command { this.name = options.name; this.description = options.description; this.emoji = options.emoji || ':robot:'; + this.guildOnly = options.guildOnly; + this.permissions = options.permissions ?? null; this.builder = options.builder .setName(this.name) .setDescription(this.description); @@ -43,4 +47,25 @@ export abstract class Command { await interaction.reply({ content: message }); } + + public hasPermission(interaction: ChatInputCommandInteraction): boolean | string { + if (!this.permissions || !interaction.channel) { + return true; + } + + if (interaction.channel instanceof DMChannel || interaction.channel.partial) { + return true; + } + + const missingPermissions = interaction.channel.permissionsFor(interaction.user.id)?.missing(PermissionsBitField.resolve(this.permissions)); + if (!missingPermissions?.length) { + return true; + } + + if (missingPermissions.length === 1) { + return `The command ${this.name} requires you to have the ${missingPermissions[0]} permission.`; + } + + return `The command ${this.name} requires you to have the ${missingPermissions.join(', ')} permissions.`; + } } diff --git a/src/base/command/CommandDispatcher.ts b/src/base/command/CommandDispatcher.ts index 8823458..2063991 100644 --- a/src/base/command/CommandDispatcher.ts +++ b/src/base/command/CommandDispatcher.ts @@ -24,7 +24,18 @@ export class CommandDispatcher { return; } + if (command.guildOnly && !interaction.inGuild()) { + await interaction.reply({ content: 'I can only run this from a server.' }); + return; + } + try { + const hasPermission = command.hasPermission(interaction); + if (typeof hasPermission === 'string') { + await interaction.reply({ content: hasPermission }); + return; + } + this.client.emit('commandExecute', command, interaction); await command.run(interaction); } catch (error) { diff --git a/src/commands/test/TestCommand.ts b/src/commands/test/TestCommand.ts index 4d139f3..d2369ec 100644 --- a/src/commands/test/TestCommand.ts +++ b/src/commands/test/TestCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, PermissionsBitField, SlashCommandBuilder } from 'discord.js'; import { Command } from '../../base/command/Command'; import { ExtendedClient } from '../../base/client/ExtendedClient'; @@ -7,6 +7,8 @@ export default class TestCommand extends Command { super(client, { name: 'test', description: 'Test', + guildOnly: true, + permissions: [PermissionsBitField.Flags.ManageChannels] as const, builder: new SlashCommandBuilder() }); } From aba1e6283ca4fb7064f32154be0395b429128046 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Thu, 1 Aug 2024 16:25:49 -0500 Subject: [PATCH 05/52] Added tests for currently implemented code. --- __mocks__/@moonstar/logger.ts | 9 + __mocks__/require-all.ts | 1 + src/app/client.spec.ts | 75 ++++++++ src/app/deploy.spec.ts | 44 +++++ src/base/client/ClientEventHandlers.spec.ts | 181 ++++++++++++++++++++ src/base/client/ClientEventHandlers.ts | 12 +- src/base/client/ExtendedClient.spec.ts | 34 ++++ src/base/command/Command.spec.ts | 174 +++++++++++++++++++ src/base/command/Command.ts | 8 +- src/base/command/CommandDeployer.spec.ts | 67 ++++++++ src/base/command/CommandDispatcher.spec.ts | 118 +++++++++++++ src/base/command/CommandRegistry.spec.ts | 135 +++++++++++++++ src/base/command/CommandRegistry.ts | 24 +-- src/base/command/validators.spec.ts | 44 +++++ src/base/command/validators.ts | 6 +- src/base/error/command.spec.ts | 23 +++ src/commands/test/TestCommand.ts | 4 +- src/config/app.spec.ts | 11 +- src/config/context.spec.ts | 35 ++++ src/entrypoint/deploy.spec.ts | 64 +++++++ src/entrypoint/start.spec.ts | 35 ++++ src/entrypoint/startSharded.spec.ts | 45 +++++ src/entrypoint/startSharded.ts | 2 +- 23 files changed, 1117 insertions(+), 34 deletions(-) create mode 100644 __mocks__/@moonstar/logger.ts create mode 100644 __mocks__/require-all.ts create mode 100644 src/app/client.spec.ts create mode 100644 src/app/deploy.spec.ts create mode 100644 src/base/client/ClientEventHandlers.spec.ts create mode 100644 src/base/client/ExtendedClient.spec.ts create mode 100644 src/base/command/Command.spec.ts create mode 100644 src/base/command/CommandDeployer.spec.ts create mode 100644 src/base/command/CommandDispatcher.spec.ts create mode 100644 src/base/command/CommandRegistry.spec.ts create mode 100644 src/base/command/validators.spec.ts create mode 100644 src/base/error/command.spec.ts create mode 100644 src/config/context.spec.ts create mode 100644 src/entrypoint/deploy.spec.ts create mode 100644 src/entrypoint/start.spec.ts create mode 100644 src/entrypoint/startSharded.spec.ts diff --git a/__mocks__/@moonstar/logger.ts b/__mocks__/@moonstar/logger.ts new file mode 100644 index 0000000..d1025df --- /dev/null +++ b/__mocks__/@moonstar/logger.ts @@ -0,0 +1,9 @@ +export default { + log: jest.fn(), + info: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), + clear: jest.fn() +}; diff --git a/__mocks__/require-all.ts b/__mocks__/require-all.ts new file mode 100644 index 0000000..cff88d1 --- /dev/null +++ b/__mocks__/require-all.ts @@ -0,0 +1 @@ +export default jest.fn().mockReturnValue({}); diff --git a/src/app/client.spec.ts b/src/app/client.spec.ts new file mode 100644 index 0000000..5825b9d --- /dev/null +++ b/src/app/client.spec.ts @@ -0,0 +1,75 @@ +import { createClient } from './client'; +import { GatewayIntentBits } from 'discord.js'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import * as ClientEventHandlers from '../base/client/ClientEventHandlers'; +import * as context from '../config/context'; + +const client = { + registry: { + registerIn: jest.fn() + }, + on: jest.fn() +}; + +jest.mock('../base/client/ExtendedClient', () => { + return { + ExtendedClient: jest.fn().mockImplementation(() => { + return client; + }) + }; +}); + +describe('App > Client', () => { + describe('createClient()', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(createClient).toBeDefined(); + }); + + it('should create a client with the correct intents.', () => { + createClient(); + + expect(ExtendedClient).toHaveBeenCalledWith({ + intents: [GatewayIntentBits.Guilds] + }); + }); + + it('should register commands in the correct folder.', () => { + createClient(); + + const call = client.registry.registerIn.mock.calls[0]; + expect(call[0]).toMatch(/commands$/); + }); + + it('should register debug event if debug is enabled.', () => { + Object.defineProperty(context, 'DEBUG_ENABLED', { value: true }); + createClient(); + + expect(client.on).toHaveBeenCalledWith('debug', ClientEventHandlers.handleDebug); + }); + + it('should not register debug event if debug is disabled.', () => { + Object.defineProperty(context, 'DEBUG_ENABLED', { value: false }); + createClient(); + + expect(client.on).not.toHaveBeenCalledWith('debug', ClientEventHandlers.handleDebug); + }); + + it('should register client events.', () => { + createClient(); + + expect(client.on).toHaveBeenCalledWith('error', ClientEventHandlers.handleError); + expect(client.on).toHaveBeenCalledWith('guildCreate', ClientEventHandlers.handleGuildCreate); + expect(client.on).toHaveBeenCalledWith('guildDelete', ClientEventHandlers.handleGuildDelete); + expect(client.on).toHaveBeenCalledWith('guildUnavailable', ClientEventHandlers.handleGuildUnavailable); + expect(client.on).toHaveBeenCalledWith('ready', ClientEventHandlers.handleReady); + expect(client.on).toHaveBeenCalledWith('warn', ClientEventHandlers.handleWarn); + expect(client.on).toHaveBeenCalledWith('commandExecute', ClientEventHandlers.handleCommandExecute); + expect(client.on).toHaveBeenCalledWith('commandError', ClientEventHandlers.handleCommandError); + expect(client.on).toHaveBeenCalledWith('commandRegistered', ClientEventHandlers.handleCommandRegistered); + }); + }); +}); diff --git a/src/app/deploy.spec.ts b/src/app/deploy.spec.ts new file mode 100644 index 0000000..84a2bac --- /dev/null +++ b/src/app/deploy.spec.ts @@ -0,0 +1,44 @@ +import { createDeployClient } from './deploy'; +import { GatewayIntentBits } from 'discord.js'; +import { ExtendedClient } from '../base/client/ExtendedClient'; + +const client = { + registry: { + registerIn: jest.fn() + } +}; + +jest.mock('../base/client/ExtendedClient', () => { + return { + ExtendedClient: jest.fn().mockImplementation(() => { + return client; + }) + }; +}); + +describe('App > Deploy', () => { + describe('createDeployClient()', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(createDeployClient).toBeDefined(); + }); + + it('should create a client with the correct intents.', () => { + createDeployClient(); + + expect(ExtendedClient).toHaveBeenCalledWith({ + intents: [GatewayIntentBits.Guilds] + }); + }); + + it('should register commands in the correct folder.', () => { + createDeployClient(); + + const call = client.registry.registerIn.mock.calls[0]; + expect(call[0]).toMatch(/commands$/); + }); + }); +}); diff --git a/src/base/client/ClientEventHandlers.spec.ts b/src/base/client/ClientEventHandlers.spec.ts new file mode 100644 index 0000000..eff9630 --- /dev/null +++ b/src/base/client/ClientEventHandlers.spec.ts @@ -0,0 +1,181 @@ +import * as ClientEventHandlers from './ClientEventHandlers'; +import logger from '@moonstar-x/logger'; +import { ChatInputCommandInteraction, Guild, GuildMember, User } from 'discord.js'; +import { Command } from '../command/Command'; + +jest.mock('@moonstar-x/logger'); + +describe('Base > Client > ClientEventHandlers', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('ClientEventHandlers.handleDebug()', () => { + it('should be defined.', () => { + expect(ClientEventHandlers.handleDebug).toBeDefined(); + }); + + it('should log debug message.', () => { + const message = 'something'; + ClientEventHandlers.handleDebug(message); + + expect(logger.debug).toHaveBeenCalledWith(message); + }); + }); + + describe('ClientEventHandlers.handleError()', () => { + it('should be defined.', () => { + expect(ClientEventHandlers.handleError).toBeDefined(); + }); + + it('should log error.', () => { + const error = new Error('Oops!'); + ClientEventHandlers.handleError(error); + + expect(logger.error).toHaveBeenCalledWith(error); + }); + }); + + describe('ClientEventHandlers.handleGuildCreate()', () => { + const guild = { name: 'Guild' } as Guild; + + it('should be defined.', () => { + expect(ClientEventHandlers.handleGuildCreate).toBeDefined(); + }); + + it('should log guild join.', () => { + ClientEventHandlers.handleGuildCreate(guild); + expect(logger.info).toHaveBeenCalledWith('Joined guild Guild.'); + }); + }); + + describe('ClientEventHandlers.handleGuildDelete()', () => { + const guild = { name: 'Guild' } as Guild; + + it('should be defined.', () => { + expect(ClientEventHandlers.handleGuildDelete).toBeDefined(); + }); + + it('should log guild leave.', () => { + ClientEventHandlers.handleGuildDelete(guild); + expect(logger.info).toHaveBeenCalledWith('Left guild Guild.'); + }); + }); + + describe('ClientEventHandlers.handleGuildUnavailable()', () => { + const guild = { name: 'Guild' } as Guild; + + it('should be defined.', () => { + expect(ClientEventHandlers.handleGuildUnavailable).toBeDefined(); + }); + + it('should log guild unavailable.', () => { + ClientEventHandlers.handleGuildUnavailable(guild); + expect(logger.warn).toHaveBeenCalledWith('Guild Guild is unavailable.'); + }); + }); + + describe('ClientEventHandlers.handleReady()', () => { + it('should be defined.', () => { + expect(ClientEventHandlers.handleReady).toBeDefined(); + }); + + it('should log ready message.', () => { + ClientEventHandlers.handleReady(); + expect(logger.info).toHaveBeenCalledWith('Connected to Discord - Ready!'); + }); + }); + + describe('ClientEventHandlers.handleWarn()', () => { + it('should be defined.', () => { + expect(ClientEventHandlers.handleWarn).toBeDefined(); + }); + + it('should log warning.', () => { + const message = 'Warning'; + ClientEventHandlers.handleWarn(message); + + expect(logger.warn).toHaveBeenCalledWith(message); + }); + }); + + describe('ClientEventHandlers.handleCommandExecute()', () => { + const member = Object.create(GuildMember.prototype); + Object.defineProperty(member, 'displayName', { value: 'Author' }); + + const user = Object.create(User.prototype); + Object.defineProperty(user, 'displayName', { value: 'Author' }); + + const command = { name: 'Command' } as Command; + const interaction = { + guild: { name: 'Guild' } as Guild, + member, + user + } as ChatInputCommandInteraction; + + it('should be defined.', () => { + expect(ClientEventHandlers.handleCommandExecute).toBeDefined(); + }); + + it('should log execution with member displayName if author is GuildMember.', () => { + ClientEventHandlers.handleCommandExecute(command, interaction); + expect(logger.info).toHaveBeenCalledWith('User Author issued command Command in Guild.'); + }); + + it('should log execution with user displayName if author is User.', () => { + ClientEventHandlers.handleCommandExecute(command, { ...interaction, member: null } as ChatInputCommandInteraction); + expect(logger.info).toHaveBeenCalledWith('User Author issued command Command in Guild.'); + }); + + it('should log execution with unknown guild name if no guild exists in interaction.', () => { + ClientEventHandlers.handleCommandExecute(command, { ...interaction, guild: null } as ChatInputCommandInteraction); + expect(logger.info).toHaveBeenCalledWith('User Author issued command Command in Unknown Guild or DM.'); + }); + }); + + describe('ClientEventHandlers.handleCommandError()', () => { + const error = new Error('Oops!'); + const command = { name: 'Command' } as Command; + const interaction = { + id: '123', + guild: { name: 'Guild' } as Guild + } as ChatInputCommandInteraction; + + it('should be defined.', () => { + expect(ClientEventHandlers.handleCommandError).toBeDefined(); + }); + + it('should log error.', () => { + ClientEventHandlers.handleCommandError(error, command, interaction); + expect(logger.error).toHaveBeenCalledWith(error); + }); + + it('should log error with guild name.', () => { + ClientEventHandlers.handleCommandError(error, command, interaction); + expect(logger.error).toHaveBeenCalledWith('Something happened when executing Command in Guild.'); + }); + + it('should log error with unknown guild name if no guild exists in interaction.', () => { + ClientEventHandlers.handleCommandError(error, command, { ...interaction, guild: null } as ChatInputCommandInteraction); + expect(logger.error).toHaveBeenCalledWith('Something happened when executing Command in Unknown Guild or DM.'); + }); + + it('should log error with interaction ID.', () => { + ClientEventHandlers.handleCommandError(error, command, interaction); + expect(logger.error).toHaveBeenCalledWith('Command interaction has an ID of 123.'); + }); + }); + + describe('ClientEventHandlers.handleCommandRegistered()', () => { + const command = { name: 'Command' } as Command; + + it('should be defined.', () => { + expect(ClientEventHandlers.handleCommandRegistered).toBeDefined(); + }); + + it('should log command registration.', () => { + ClientEventHandlers.handleCommandRegistered(command); + expect(logger.info).toHaveBeenCalledWith('Registered command: Command.'); + }); + }); +}); diff --git a/src/base/client/ClientEventHandlers.ts b/src/base/client/ClientEventHandlers.ts index 45b5f7b..7942a60 100644 --- a/src/base/client/ClientEventHandlers.ts +++ b/src/base/client/ClientEventHandlers.ts @@ -11,12 +11,12 @@ export const handleError = (error: Error) => { }; export const handleGuildCreate = (guild: Guild) => { - logger.info(`Joined guild ${guild.name}`); + logger.info(`Joined guild ${guild.name}.`); }; // TODO: Delete guild data in database. export const handleGuildDelete = (guild: Guild) => { - logger.info(`Left guild ${guild.name}`); + logger.info(`Left guild ${guild.name}.`); }; export const handleGuildUnavailable = (guild: Guild) => { @@ -24,7 +24,7 @@ export const handleGuildUnavailable = (guild: Guild) => { }; export const handleReady = () => { - logger.info('Ready!'); + logger.info('Connected to Discord - Ready!'); }; export const handleWarn = (info: string) => { @@ -35,17 +35,17 @@ export const handleCommandExecute = (command: Command, interaction: ChatInputCom const author = interaction.member instanceof GuildMember ? interaction.member.displayName : interaction.user.displayName; const guild = interaction.guild?.name || 'Unknown Guild or DM'; - logger.info(`User ${author} issued command ${command.name} in ${guild}`); + logger.info(`User ${author} issued command ${command.name} in ${guild}.`); }; export const handleCommandError = (error: unknown, command: Command, interaction: ChatInputCommandInteraction) => { const guild = interaction.guild?.name || 'Unknown Guild or DM'; logger.error(`Something happened when executing ${command.name} in ${guild}.`); - logger.error(`Command interaction has ID of ${interaction.id}`); + logger.error(`Command interaction has an ID of ${interaction.id}.`); logger.error(error); }; export const handleCommandRegistered = (command: Command) => { - logger.info(`Registered command: ${command.name}`); + logger.info(`Registered command: ${command.name}.`); }; diff --git a/src/base/client/ExtendedClient.spec.ts b/src/base/client/ExtendedClient.spec.ts new file mode 100644 index 0000000..a6f560b --- /dev/null +++ b/src/base/client/ExtendedClient.spec.ts @@ -0,0 +1,34 @@ +import { ExtendedClient } from './ExtendedClient'; +import { GatewayIntentBits, Interaction } from 'discord.js'; + +jest.mock('../command/CommandDispatcher', () => { + return { + CommandDispatcher: jest.fn().mockImplementation(() => { + return { + handleInteraction: jest.fn() + }; + }) + }; +}); + +describe('Base > Client > ExtendedClient', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('class ExtendedClient', () => { + it('should be defined.', () => { + expect(ExtendedClient).toBeDefined(); + }); + + describe('constructor', () => { + it('should register interactionCreate on construction.', () => { + const client = new ExtendedClient({ intents: [] as ReadonlyArray<GatewayIntentBits> }); + const interaction = {} as Interaction; + + client.emit('interactionCreate', interaction); + expect(client.dispatcher.handleInteraction).toHaveBeenCalledWith(interaction); + }); + }); + }); +}); diff --git a/src/base/command/Command.spec.ts b/src/base/command/Command.spec.ts new file mode 100644 index 0000000..5923ac5 --- /dev/null +++ b/src/base/command/Command.spec.ts @@ -0,0 +1,174 @@ +import { Command } from './Command'; +import { ChatInputCommandInteraction, DMChannel, SlashCommandBuilder } from 'discord.js'; +import { ExtendedClient } from '../client/ExtendedClient'; + +describe('Base > Command > Command', () => { + const client = { + emit: jest.fn() + } as unknown as ExtendedClient; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('class Command', () => { + class ConcreteCommand extends Command { + public constructor(client: ExtendedClient) { + super(client, { + name: 'command', + description: 'description', + emoji: ':a:', + guildOnly: true, + permissions: ['ManageGuild', 'ManageChannels'] as const, + builder: new SlashCommandBuilder() + }); + } + + public override run() { + throw new Error(); + } + } + + it('should be defined.', () => { + expect(Command).toBeDefined(); + }); + + describe('constructor', () => { + it('should set options as properties.', () => { + const command = new ConcreteCommand(client); + + expect(command.name).toBe('command'); + expect(command.description).toBe('description'); + expect(command.emoji).toBe(':a:'); + expect(command.guildOnly).toBe(true); + expect(command.permissions).toStrictEqual(['ManageGuild', 'ManageChannels']); + expect(command.builder).toBeInstanceOf(SlashCommandBuilder); + }); + + it('should set default options if not provided.', () => { + class ConcreteDefaultCommand extends Command { + public constructor(client: ExtendedClient) { + super(client, { + name: 'command', + description: 'description', + guildOnly: true, + builder: new SlashCommandBuilder() + }); + } + + public override run() { + throw new Error(); + } + } + + const command = new ConcreteDefaultCommand(client); + + expect(command.name).toBe('command'); + expect(command.description).toBe('description'); + expect(command.emoji).toBe(':robot:'); + expect(command.guildOnly).toBe(true); + expect(command.permissions).toBeNull(); + expect(command.builder).toBeInstanceOf(SlashCommandBuilder); + }); + + it('should set properties to builder.', () => { + const command = new ConcreteCommand(client); + + expect(command.builder.name).toBe('command'); + expect(command.builder.description).toBe('description'); + }); + }); + + describe('onError()', () => { + const command = new ConcreteCommand(client); + const error = new Error('Oops!'); + const interaction = { + editReply: jest.fn(), + reply: jest.fn() + } as unknown as ChatInputCommandInteraction; + + it('should emit commandError with error.', async () => { + await command.onError(error, interaction); + expect(client.emit).toHaveBeenCalledWith('commandError', error, command, interaction); + }); + + it('should edit reply with error message if interaction is deferred.', async () => { + await command.onError(error, { ...interaction, deferred: true } as ChatInputCommandInteraction); + expect(interaction.editReply).toHaveBeenCalledWith({ content: 'An error has occurred when running the command command.' }); + }); + + it('should edit reply with error message if interaction is replied.', async () => { + await command.onError(error, { ...interaction, replied: true } as ChatInputCommandInteraction); + expect(interaction.editReply).toHaveBeenCalledWith({ content: 'An error has occurred when running the command command.' }); + }); + + it('should reply with error message if interaction is not deferred or replied.', async () => { + await command.onError(error, { ...interaction, deferred: false, replied: false } as ChatInputCommandInteraction); + expect(interaction.reply).toHaveBeenCalledWith({ content: 'An error has occurred when running the command command.' }); + }); + }); + + describe('hasPermission()', () => { + const missingMock = jest.fn().mockReturnValue(undefined); + const permissionsForMock = jest.fn().mockReturnValue({ missing: missingMock }); + + const command = new ConcreteCommand(client); + const interaction = { + user: { + id: '123' + }, + channel: { + permissionsFor: permissionsForMock + } + } as unknown as ChatInputCommandInteraction; + + it('should return true if command has no permissions set.', () => { + const command = new ConcreteCommand(client); + Object.defineProperty(command, 'permissions', { value: null }); + + const result = command.hasPermission(interaction); + expect(result).toBe(true); + }); + + it('should return true if interaction does not come from a channel.', () => { + const result = command.hasPermission({ ...interaction, channel: null } as ChatInputCommandInteraction); + expect(result).toBe(true); + }); + + it('should return true if interaction comes from a DM channel.', () => { + const channel = Object.create(DMChannel.prototype); + const result = command.hasPermission({ ...interaction, channel } as ChatInputCommandInteraction); + + expect(result).toBe(true); + }); + + it('should return true if interaction comes from a partial channel.', () => { + const channel = { partial: true }; + const result = command.hasPermission({ ...interaction, channel } as ChatInputCommandInteraction); + + expect(result).toBe(true); + }); + + it('should return true if no permissions are missing.', () => { + missingMock.mockReturnValueOnce([]); + const result = command.hasPermission(interaction); + + expect(result).toBe(true); + }); + + it('should return error message if 1 permission is missing.', () => { + missingMock.mockReturnValueOnce(['ManageGuild']); + const result = command.hasPermission(interaction); + + expect(result).toBe('The command command requires you to have the permissions: ManageGuild.'); + }); + + it('should return error message if multiple permissions are missing.', () => { + missingMock.mockReturnValueOnce(['ManageGuild', 'ManageChannels']); + const result = command.hasPermission(interaction); + + expect(result).toBe('The command command requires you to have the permissions: ManageGuild, ManageChannels.'); + }); + }); + }); +}); diff --git a/src/base/command/Command.ts b/src/base/command/Command.ts index c8d1ae7..df549ff 100644 --- a/src/base/command/Command.ts +++ b/src/base/command/Command.ts @@ -38,7 +38,7 @@ export abstract class Command { public async onError(error: unknown, interaction: ChatInputCommandInteraction): Promise<void> { this.client.emit('commandError', error, this, interaction); - const message = `An error has occurred when running the command ${this.name}`; + const message = `An error has occurred when running the command ${this.name}.`; if (interaction.deferred || interaction.replied) { await interaction.editReply({ content: message }); @@ -62,10 +62,6 @@ export abstract class Command { return true; } - if (missingPermissions.length === 1) { - return `The command ${this.name} requires you to have the ${missingPermissions[0]} permission.`; - } - - return `The command ${this.name} requires you to have the ${missingPermissions.join(', ')} permissions.`; + return `The command ${this.name} requires you to have the permissions: ${missingPermissions.join(', ')}.`; } } diff --git a/src/base/command/CommandDeployer.spec.ts b/src/base/command/CommandDeployer.spec.ts new file mode 100644 index 0000000..ba302d9 --- /dev/null +++ b/src/base/command/CommandDeployer.spec.ts @@ -0,0 +1,67 @@ +import { CommandDeployer } from './CommandDeployer'; +import { Command } from './Command'; +import { CommandRegistry } from './CommandRegistry'; +import { REST } from 'discord.js'; +import { Routes } from 'discord-api-types/v10'; + +jest.mock('discord.js', () => { + const restMock: Record<string, jest.Mock> = { + put: jest.fn(), + get: jest.fn() + }; + restMock.setToken = jest.fn().mockReturnValue(restMock); + + return { + ...jest.requireActual('discord.js'), + REST: jest.fn().mockReturnValue(restMock) + }; +}); + +describe('Base > Command > CommandDeployer', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('class CommandDeployer', () => { + let deployer: CommandDeployer; + let deployerRest: REST; + + const command = { + builder: 'builder' + } as unknown as Command; + const registry = { + getAll: jest.fn().mockReturnValue([command]) + } as unknown as CommandRegistry; + + beforeEach(() => { + deployer = new CommandDeployer('token'); + deployerRest = (deployer as unknown as { rest: REST }).rest; + }); + + it('should be defined.', () => { + expect(CommandDeployer).toBeDefined(); + }); + + describe('deployGlobally()', () => { + beforeEach(() => { + (deployerRest.get as jest.Mock).mockResolvedValue({ id: '123' }); + }); + + it('should deploy all commands.', async () => { + await deployer.deployGlobally(registry); + expect(deployerRest.put).toHaveBeenCalledWith(Routes.applicationCommands('123'), { body: ['builder'] }); + }); + }); + + describe('deployToGuild()', () => { + beforeEach(() => { + (deployerRest.get as jest.Mock).mockResolvedValue({ id: '123' }); + }); + + it('should deploy all commands.', async () => { + await deployer.deployToGuild(registry, 'guild'); + expect(deployerRest.put).toHaveBeenCalledWith(Routes.applicationGuildCommands('123', 'guild'), { body: ['builder'] }); + }); + }); + }); +}); diff --git a/src/base/command/CommandDispatcher.spec.ts b/src/base/command/CommandDispatcher.spec.ts new file mode 100644 index 0000000..21d61c8 --- /dev/null +++ b/src/base/command/CommandDispatcher.spec.ts @@ -0,0 +1,118 @@ +import { CommandDispatcher } from './CommandDispatcher'; +import { ChatInputCommandInteraction } from 'discord.js'; +import logger from '@moonstar-x/logger'; +import { ExtendedClient } from '../client/ExtendedClient'; +import { Command } from './Command'; + +jest.mock('@moonstar-x/logger'); + +describe('Base > Command > CommandDispatcher', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('class CommandDispatcher', () => { + const client = { + emit: jest.fn(), + registry: { + get: jest.fn() + } + } as unknown as ExtendedClient; + + it('should be defined.', () => { + expect(CommandDispatcher).toBeDefined(); + }); + + describe('handleInteraction()', () => { + const dispatcher = new CommandDispatcher(client, client.registry); + + const interaction = { + isChatInputCommand: jest.fn().mockReturnValue(true), + inGuild: jest.fn().mockReturnValue(true), + reply: jest.fn() + } as unknown as ChatInputCommandInteraction; + + const command = { + name: 'command', + guildOnly: false, + run: jest.fn(), + hasPermission: jest.fn().mockReturnValue(true), + onError: jest.fn() + } as unknown as Command; + + beforeAll(() => { + (client.registry.get as jest.Mock).mockReturnValue(command); + }); + + afterAll(() => { + (client.registry.get as jest.Mock).mockReset(); + }); + + it('should not execute command if interaction is not a chat input command.', async () => { + (interaction.isChatInputCommand as unknown as jest.Mock).mockReturnValueOnce(false); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).not.toHaveBeenCalled(); + expect(command.run).not.toHaveBeenCalled(); + }); + + it('should not execute command if registry cannot find it.', async () => { + (client.registry.get as jest.Mock).mockReturnValueOnce(null); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).not.toHaveBeenCalled(); + expect(command.run).not.toHaveBeenCalled(); + }); + + it('should reply interaction and not execute command if command is guildOnly and interaction was not sent in guild.', async () => { + (client.registry.get as jest.Mock).mockReturnValueOnce({ ...command, guildOnly: true }); + (interaction.inGuild as unknown as jest.Mock).mockReturnValueOnce(false); + await dispatcher.handleInteraction(interaction); + + expect(interaction.reply).toHaveBeenCalledWith({ content: 'I can only run this from a server.' }); + expect(client.emit).not.toHaveBeenCalled(); + expect(command.run).not.toHaveBeenCalled(); + }); + + it('should reply interaction and not execute command if author has no permission to execute.', async () => { + (command.hasPermission as jest.Mock).mockReturnValueOnce('No permissions.'); + await dispatcher.handleInteraction(interaction); + + expect(interaction.reply).toHaveBeenCalledWith({ content: 'No permissions.' }); + expect(client.emit).not.toHaveBeenCalled(); + expect(command.run).not.toHaveBeenCalled(); + }); + + it('should execute command if all is well.', async () => { + await dispatcher.handleInteraction(interaction); + + expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); + expect(command.run).toHaveBeenCalledWith(interaction); + }); + + it('should handle error with command.onError if run fails.', async () => { + const error = new Error('Oops!'); + (command.run as jest.Mock).mockRejectedValueOnce(error); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); + expect(command.run).toHaveBeenCalledWith(interaction); + expect(command.onError).toHaveBeenCalledWith(error, interaction); + }); + + it('should log error if command.onError fails.', async () => { + const originalError = new Error('Oops!'); + const innerError = new Error('Ouch!'); + (command.run as jest.Mock).mockRejectedValueOnce(originalError); + (command.onError as jest.Mock).mockRejectedValueOnce(innerError); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); + expect(command.run).toHaveBeenCalledWith(interaction); + expect(command.onError).toHaveBeenCalledWith(originalError, interaction); + expect(logger.error).toHaveBeenCalledWith('There was an error in the command execution for command.', originalError); + expect(logger.error).toHaveBeenCalledWith('Additionally, the error handling could not complete.', innerError); + }); + }); + }); +}); diff --git a/src/base/command/CommandRegistry.spec.ts b/src/base/command/CommandRegistry.spec.ts new file mode 100644 index 0000000..c84c82a --- /dev/null +++ b/src/base/command/CommandRegistry.spec.ts @@ -0,0 +1,135 @@ +import { CommandRegistry } from './CommandRegistry'; +import { ExtendedClient } from '../client/ExtendedClient'; +import { Command } from './Command'; +import { CommandRegistryError, CommandValidationError } from '../error/command'; +import requireAll from 'require-all'; + +jest.mock('require-all'); + +describe('Base > Command > CommandRegistry', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('class CommandRegistry', () => { + let registry: CommandRegistry; + + const client = { + emit: jest.fn() + } as unknown as ExtendedClient; + + const command = { + name: 'command', + description: 'A little description.' + } as Command; + const command2 = { + name: 'cmd', + description: 'A little description.' + } as Command; + + beforeEach(() => { + registry = new CommandRegistry(client); + }); + + it('should be defined.', () => { + expect(CommandRegistry).toBeDefined(); + }); + + describe('register()', () => { + it('should throw CommandValidationError if command is not valid.', () => { + expect(() => registry.register({ ...command, name: '' } as Command)).toThrowError(CommandValidationError); + }); + + it('should throw CommandRegistryError if command is already registered.', () => { + const expectedError = new CommandRegistryError('Command command is already registered. Command names must be unique.'); + registry.register(command); + + expect(() => registry.register(command)).toThrowError(expectedError); + }); + + it('should set the command.', () => { + registry.register(command); + expect(registry.get('command')).toBe(command); + }); + + it('should emit client.commandRegistered event.', () => { + registry.register(command); + expect(client.emit).toHaveBeenCalledWith('commandRegistered', command); + }); + + it('should set multiple commands if array provided.', () => { + registry.register([command, command2]); + expect(registry.get('command')).toBe(command); + expect(registry.get('cmd')).toBe(command2); + }); + }); + + describe('get()', () => { + beforeEach(() => { + registry.register(command); + }); + + it('should return command if exists.', () => { + expect(registry.get('command')).toBe(command); + }); + + it('should return undefined if command does not exist.', () => { + expect(registry.get('unknown')).toBeUndefined(); + }); + }); + + describe('getAll()', () => { + beforeEach(() => { + registry.register([command, command2]); + }); + + it('should return an array of commands.', () => { + expect(registry.getAll()).toStrictEqual([command, command2]); + }); + }); + + describe('size()', () => { + it('should return the number of commands registered.', () => { + expect(registry.size()).toBe(0); + registry.register(command); + expect(registry.size()).toBe(1); + registry.register(command2); + expect(registry.size()).toBe(2); + }); + }); + + describe('registerIn()', () => { + beforeAll(() => { + (requireAll as jest.Mock).mockReturnValue({ + folder: { + 'file.ts': { + default: jest.fn().mockReturnValue(command) + } + }, + folder2: { + folder3: { + folder4: { + folder5: { + 'file.ts': { + NamedExport: jest.fn().mockReturnValue(command2) + } + } + } + } + } + }); + }); + + afterAll(() => { + (requireAll as jest.Mock).mockReset(); + }); + + it('should register commands found.', () => { + registry.registerIn('directory'); + + expect(registry.get('command')).toBe(command); + expect(registry.get('cmd')).toBe(command2); + }); + }); + }); +}); diff --git a/src/base/command/CommandRegistry.ts b/src/base/command/CommandRegistry.ts index 77e00d8..a813e85 100644 --- a/src/base/command/CommandRegistry.ts +++ b/src/base/command/CommandRegistry.ts @@ -16,18 +16,6 @@ export class CommandRegistry { this.commands = new Collection(); } - public get(name: string): Command | undefined { - return this.commands.get(name); - } - - public getAll(): Command[] { - return Array.from(this.commands.values()); - } - - public size(): number { - return this.commands.size; - } - public register(command: Command): this; public register(commands: Command[]): this; public register(command: Command | Command[]): this { @@ -40,6 +28,18 @@ export class CommandRegistry { return this; } + public get(name: string): Command | undefined { + return this.commands.get(name); + } + + public getAll(): Command[] { + return Array.from(this.commands.values()); + } + + public size(): number { + return this.commands.size; + } + public registerIn(path: string): this { const commandModules: Record<string, Class<Command>> = flatten( requireAll({ diff --git a/src/base/command/validators.spec.ts b/src/base/command/validators.spec.ts new file mode 100644 index 0000000..520af15 --- /dev/null +++ b/src/base/command/validators.spec.ts @@ -0,0 +1,44 @@ +import { validateCommand } from './validators'; +import { Command } from './Command'; +import { CommandValidationError } from '../error/command'; + +describe('Base > Command > Validators', () => { + describe('validateCommand()', () => { + const command = { + name: 'command', + description: 'A little description.' + } as Command; + + it('should be defined.', () => { + expect(validateCommand).toBeDefined(); + }); + + it('should throw CommandValidationError if name is too short.', () => { + const expectedError = new CommandValidationError('Command / has an invalid name. It must be between 1 and 32 characters.'); + expect(() => validateCommand({ ...command, name: '' } as Command)).toThrow(expectedError); + }); + + it('should throw CommandValidationError if name is too long.', () => { + const name = 'a'.repeat(35); + const expectedError = new CommandValidationError(`Command /${name} has an invalid name. It must be between 1 and 32 characters.`); + expect(() => validateCommand({ ...command, name } as Command)).toThrow(expectedError); + }); + + it('should throw CommandValidationError if name contains upper case letters.', () => { + const name = 'COmmand'; + const expectedError = new CommandValidationError(`Command /${name} has an invalid name. Please use all lower-cased characters.`); + expect(() => validateCommand({ ...command, name } as Command)).toThrow(expectedError); + }); + + it('should throw CommandValidationError if description is too short.', () => { + const expectedError = new CommandValidationError('Command /command has an invalid description. It must be between 1 and 100 characters.'); + expect(() => validateCommand({ ...command, description: '' } as Command)).toThrow(expectedError); + }); + + it('should throw CommandValidationError if description is too long.', () => { + const description = 'a'.repeat(101); + const expectedError = new CommandValidationError('Command /command has an invalid description. It must be between 1 and 100 characters.'); + expect(() => validateCommand({ ...command, description } as Command)).toThrow(expectedError); + }); + }); +}); diff --git a/src/base/command/validators.ts b/src/base/command/validators.ts index 95c6f1d..6aeb905 100644 --- a/src/base/command/validators.ts +++ b/src/base/command/validators.ts @@ -10,17 +10,17 @@ const DESCRIPTION_MAX = 100; const validateName = (name: string) => { if (name.length < NAME_MIN || name.length > NAME_MAX) { - throw new CommandValidationError(`${name} is not a valid command name. It must be between ${NAME_MIN} and ${NAME_MAX} characters.`); + throw new CommandValidationError(`Command /${name} has an invalid name. It must be between ${NAME_MIN} and ${NAME_MAX} characters.`); } if (!NAME_PATTERN.test(name)) { - throw new CommandValidationError(`${name} is not a valid command name. Please use all lower-cased characters.`); + throw new CommandValidationError(`Command /${name} has an invalid name. Please use all lower-cased characters.`); } }; const validateDescription = (name: string, description: string) => { if (description.length < DESCRIPTION_MIN || description.length > DESCRIPTION_MAX) { - throw new CommandValidationError(`${name} does not have a valid description. It must be between ${DESCRIPTION_MIN} and ${DESCRIPTION_MAX} characters.`); + throw new CommandValidationError(`Command /${name} has an invalid description. It must be between ${DESCRIPTION_MIN} and ${DESCRIPTION_MAX} characters.`); } }; diff --git a/src/base/error/command.spec.ts b/src/base/error/command.spec.ts new file mode 100644 index 0000000..9bf0342 --- /dev/null +++ b/src/base/error/command.spec.ts @@ -0,0 +1,23 @@ +import { CommandRegistryError, CommandValidationError } from './command'; + +describe('Base > Error > Command', () => { + describe('class CommandRegistryError', () => { + it('should be defined.', () => { + expect(CommandRegistryError).toBeDefined(); + }); + + it('should return an instance of Error.', () => { + expect(new CommandRegistryError()).toBeInstanceOf(Error); + }); + }); + + describe('class CommandValidationError', () => { + it('should be defined.', () => { + expect(CommandValidationError).toBeDefined(); + }); + + it('should return an instance of Error.', () => { + expect(new CommandValidationError()).toBeInstanceOf(Error); + }); + }); +}); diff --git a/src/commands/test/TestCommand.ts b/src/commands/test/TestCommand.ts index d2369ec..5646eaa 100644 --- a/src/commands/test/TestCommand.ts +++ b/src/commands/test/TestCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, PermissionsBitField, SlashCommandBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; import { Command } from '../../base/command/Command'; import { ExtendedClient } from '../../base/client/ExtendedClient'; @@ -8,7 +8,7 @@ export default class TestCommand extends Command { name: 'test', description: 'Test', guildOnly: true, - permissions: [PermissionsBitField.Flags.ManageChannels] as const, + permissions: [PermissionFlagsBits.ManageChannels] as const, builder: new SlashCommandBuilder() }); } diff --git a/src/config/app.spec.ts b/src/config/app.spec.ts index b936bfd..f8c0c53 100644 --- a/src/config/app.spec.ts +++ b/src/config/app.spec.ts @@ -8,16 +8,19 @@ describe('Config > App', () => { DISCORD_TOKEN: 'token' }; - beforeAll(() => { - process.env = mockedEnv; + const resetModule = () => { jest.resetModules(); config = require('./app'); + }; + + beforeAll(() => { + process.env = mockedEnv; + resetModule(); }); afterAll(() => { process.env = oldEnv; - jest.resetModules(); - config = require('./app'); + resetModule(); }); it('should export valid DISCORD_TOKEN.', () => { diff --git a/src/config/context.spec.ts b/src/config/context.spec.ts new file mode 100644 index 0000000..bd36ca0 --- /dev/null +++ b/src/config/context.spec.ts @@ -0,0 +1,35 @@ +import * as configModule from './context'; + +describe('Config > Context', () => { + let config: typeof configModule; + + const oldArgv = [...process.argv]; + + const resetModule = () => { + jest.resetModules(); + config = require('./context'); + }; + + beforeAll(() => { + resetModule(); + }); + + afterEach(() => { + process.argv = oldArgv; + resetModule(); + }); + + it('should export valid DEBUG_ENABLED if argv contains --debug.', () => { + process.argv = ['--arg', '--debug']; + resetModule(); + + expect(config.DEBUG_ENABLED).toBe(true); + }); + + it('should export valid DEBUG_ENABLED if argv does not contain --debug.', () => { + process.argv = ['--arg']; + resetModule(); + + expect(config.DEBUG_ENABLED).toBe(false); + }); +}); diff --git a/src/entrypoint/deploy.spec.ts b/src/entrypoint/deploy.spec.ts new file mode 100644 index 0000000..1015905 --- /dev/null +++ b/src/entrypoint/deploy.spec.ts @@ -0,0 +1,64 @@ +import { ExtendedClient } from '../base/client/ExtendedClient'; +import logger from '@moonstar-x/logger'; +import { CommandDeployer } from '../base/command/CommandDeployer'; + +const client = { + registry: {} +} as unknown as ExtendedClient; + +const deployer = { + deployGlobally: jest.fn().mockResolvedValue([ + { name: 'cmd1' }, + { name: 'cmd2' } + ]) +} as unknown as CommandDeployer; + +jest.mock('@moonstar-x/logger'); + +jest.mock('../app/deploy', () => { + return { + createDeployClient: jest.fn().mockReturnValue(client) + }; +}); + +jest.mock('../base/command/CommandDeployer', () => { + return { + CommandDeployer: jest.fn().mockReturnValue(deployer) + }; +}); + +describe('Entrypoint > Deploy', () => { + const load = async () => { + jest.isolateModules(() => { + require('./deploy'); + }); + await Promise.resolve(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should deploy all commands globally.', async () => { + await load(); + expect(deployer.deployGlobally).toHaveBeenCalledWith(client.registry); + }); + + it('should log deployment messages.', async () => { + await load(); + + expect(logger.info).toHaveBeenCalledWith('Successfully deployed cmd1 command globally.'); + expect(logger.info).toHaveBeenCalledWith('Successfully deployed cmd2 command globally.'); + expect(logger.info).toHaveBeenCalledWith('Finished deploying 2 commands globally. These changes can take up to an hour to be reflected on Discord.'); + }); + + it('should log error messages if deployment fails.', async () => { + const error = new Error('Oops'); + (deployer.deployGlobally as jest.Mock).mockRejectedValueOnce(error); + + await load(); + + expect(logger.error).toHaveBeenCalledWith('Could not deploy commands globally.'); + expect(logger.error).toHaveBeenCalledWith(error); + }); +}); diff --git a/src/entrypoint/start.spec.ts b/src/entrypoint/start.spec.ts new file mode 100644 index 0000000..f9d2821 --- /dev/null +++ b/src/entrypoint/start.spec.ts @@ -0,0 +1,35 @@ +import { ExtendedClient } from '../base/client/ExtendedClient'; + +const client = { + login: jest.fn() +} as unknown as ExtendedClient; + +jest.mock('../app/client', () => { + return { + createClient: jest.fn().mockReturnValue(client) + }; +}); + +jest.mock('../config/app', () => { + return { + DISCORD_TOKEN: 'token' + }; +}); + +describe('Entrypoint > Start', () => { + const load = async () => { + jest.isolateModules(() => { + require('./start'); + }); + await Promise.resolve(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should login client with token.', async () => { + await load(); + expect(client.login).toHaveBeenCalledWith('token'); + }); +}); diff --git a/src/entrypoint/startSharded.spec.ts b/src/entrypoint/startSharded.spec.ts new file mode 100644 index 0000000..ad2900d --- /dev/null +++ b/src/entrypoint/startSharded.spec.ts @@ -0,0 +1,45 @@ +import { ShardingManager } from 'discord.js'; +import EventEmitter from 'events'; +import logger from '@moonstar-x/logger'; + +const manager = new EventEmitter() as unknown as ShardingManager; +manager.spawn = jest.fn(); + +jest.mock('../config/app', () => { + return { + DISCORD_TOKEN: 'token' + }; +}); + +jest.mock('discord.js', () => { + return { + ShardingManager: jest.fn().mockReturnValue(manager) + }; +}); + +jest.mock('@moonstar-x/logger'); + +describe('Entrypoint > Start Sharded', () => { + const load = async () => { + jest.isolateModules(() => { + require('./startSharded'); + }); + await Promise.resolve(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should spawn manager.', async () => { + await load(); + expect(manager.spawn).toHaveBeenCalledWith({ amount: 'auto' }); + }); + + it('should register manager.shardCreate event handler.', async () => { + await load(); + (manager as unknown as EventEmitter).emit('shardCreate', { id: '123' }); + + expect(logger.info).toHaveBeenCalledWith('Launched shard with ID: 123.'); + }); +}); diff --git a/src/entrypoint/startSharded.ts b/src/entrypoint/startSharded.ts index a472ac3..8270746 100644 --- a/src/entrypoint/startSharded.ts +++ b/src/entrypoint/startSharded.ts @@ -9,7 +9,7 @@ const startScript = path.join(__dirname, `./start.js`); const manager = new ShardingManager(startScript, { token: DISCORD_TOKEN }); manager.on('shardCreate', (shard) => { - logger.info(`Launched shard with ID: ${shard.id}`); + logger.info(`Launched shard with ID: ${shard.id}.`); }); manager.spawn({ amount: 'auto' }); From b4ef476145db26112db4c8fc67b3ec2814254228 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 15:02:34 -0500 Subject: [PATCH 06/52] Added GameOffer model. --- src/models/offer.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/models/offer.ts diff --git a/src/models/offer.ts b/src/models/offer.ts new file mode 100644 index 0000000..3aa9781 --- /dev/null +++ b/src/models/offer.ts @@ -0,0 +1,14 @@ +export type GameOfferType = 'game' | 'dlc' | 'bundle' | 'other'; + +export interface GameOffer { + storefront: string + id: string + url: string + title: string + description: string + type: GameOfferType + publisher: string | null + original_price: number | null + original_price_fmt: string | null + thumbnail: string | null +} From ef1eccadb2b959098ea4be0dd66152556a3e2dbd Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 15:24:06 -0500 Subject: [PATCH 07/52] Added env environment with Docker Compose. --- _dev/.gitignore | 2 ++ _dev/docker-compose.yml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 _dev/.gitignore create mode 100644 _dev/docker-compose.yml diff --git a/_dev/.gitignore b/_dev/.gitignore new file mode 100644 index 0000000..17972d7 --- /dev/null +++ b/_dev/.gitignore @@ -0,0 +1,2 @@ +redis +postgres diff --git a/_dev/docker-compose.yml b/_dev/docker-compose.yml new file mode 100644 index 0000000..9a40496 --- /dev/null +++ b/_dev/docker-compose.yml @@ -0,0 +1,31 @@ +name: dev-discord-free-games-notifier + +services: + crawler: + image: ghcr.io/moonstar-x/free-games-crawler:latest + restart: no + depends_on: + - redis + environment: + REDIS_URI: redis://redis:6379 + + redis: + image: redis:7.4-rc2-alpine + restart: no + command: redis-server --save 60 1 --loglevel warning + ports: + - 6379:6379 + volumes: + - ./redis:/data + + postgres: + image: postgres:15-alpine + restart: no + ports: + - 5432:5432 + volumes: + - ./postgres:/var/lib/postgresql/data + environment: + POSTGRES_DB: dev + POSTGRES_USER: dev + POSTGRES_PASSWORD: password From 8c52bfad5942e7a72b45940df1e4ada3b696b1be Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 15:39:02 -0500 Subject: [PATCH 08/52] Added env variables in config for services. --- src/config/app.spec.ts | 53 ++++++++++++++++++++++++++++++++++++++++-- src/config/app.ts | 9 +++++++ src/global.d.ts | 11 ++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/config/app.spec.ts b/src/config/app.spec.ts index f8c0c53..fde0787 100644 --- a/src/config/app.spec.ts +++ b/src/config/app.spec.ts @@ -5,7 +5,14 @@ describe('Config > App', () => { const oldEnv = { ...process.env }; const mockedEnv = { - DISCORD_TOKEN: 'token' + DISCORD_TOKEN: 'token', + REDIS_URI: 'redis://localhost:6379', + POSTGRES_HOST: 'localhost', + POSTGRES_PORT: '5431', + POSTGRES_USER: 'user', + POSTGRES_PASSWORD: 'password', + POSTGRES_DATABASE: 'db', + POSTGRES_MAX_SHARD_CONNECTIONS: '30' }; const resetModule = () => { @@ -13,7 +20,7 @@ describe('Config > App', () => { config = require('./app'); }; - beforeAll(() => { + beforeEach(() => { process.env = mockedEnv; resetModule(); }); @@ -26,4 +33,46 @@ describe('Config > App', () => { it('should export valid DISCORD_TOKEN.', () => { expect(config.DISCORD_TOKEN).toBe('token'); }); + + it('should export valid REDIS_URI.', () => { + expect(config.REDIS_URI).toBe('redis://localhost:6379'); + }); + + it('should export valid POSTGRES_HOST.', () => { + expect(config.POSTGRES_HOST).toBe('localhost'); + }); + + it('should export valid POSTGRES_PORT.', () => { + expect(config.POSTGRES_PORT).toBe(5431); + }); + + it('should export valid POSTGRES_PORT if no value is provided.', () => { + process.env = { ...mockedEnv, POSTGRES_PORT: undefined as unknown as string }; + resetModule(); + + expect(config.POSTGRES_PORT).toBe(5432); + }); + + it('should export valid POSTGRES_USER.', () => { + expect(config.POSTGRES_USER).toBe('user'); + }); + + it('should export valid POSTGRES_PASSWORD.', () => { + expect(config.POSTGRES_PASSWORD).toBe('password'); + }); + + it('should export valid POSTGRES_DATABASE.', () => { + expect(config.POSTGRES_DATABASE).toBe('db'); + }); + + it('should export valid POSTGRES_MAX_SHARD_CONNECTIONS.', () => { + expect(config.POSTGRES_MAX_SHARD_CONNECTIONS).toBe(30); + }); + + it('should export valid POSTGRES_MAX_SHARD_CONNECTIONS if no value is provided.', () => { + process.env = { ...mockedEnv, POSTGRES_MAX_SHARD_CONNECTIONS: undefined as unknown as string }; + resetModule(); + + expect(config.POSTGRES_MAX_SHARD_CONNECTIONS).toBe(15); + }); }); diff --git a/src/config/app.ts b/src/config/app.ts index 7a44a74..19a1a63 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -1,3 +1,12 @@ import 'dotenv/config'; export const DISCORD_TOKEN = process.env.DISCORD_TOKEN!; + +export const REDIS_URI = process.env.REDIS_URI!; + +export const POSTGRES_HOST = process.env.POSTGRES_HOST!; +export const POSTGRES_PORT = process.env.POSTGRES_PORT ? parseInt(process.env.POSTGRES_PORT, 10) : 5432; +export const POSTGRES_USER = process.env.POSTGRES_USER!; +export const POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD!; +export const POSTGRES_DATABASE = process.env.POSTGRES_DATABASE!; +export const POSTGRES_MAX_SHARD_CONNECTIONS = process.env.POSTGRES_MAX_SHARD_CONNECTIONS ? parseInt(process.env.POSTGRES_MAX_SHARD_CONNECTIONS, 10) : 15; diff --git a/src/global.d.ts b/src/global.d.ts index 78fce6d..7a70569 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,7 +1,16 @@ declare global { namespace NodeJS { interface ProcessEnv { - DISCORD_TOKE?: string + DISCORD_TOKEN?: string + + REDIS_URI?: string + + POSTGRES_HOST?: string + POSTGRES_PORT?: string + POSTGRES_USER?: string + POSTGRES_PASSWORD?: string + POSTGRES_DATABASE?: string + POSTGRES_MAX_SHARD_CONNECTIONS?: string } } } From 354aac7338bc14b2b7ff1b620129875caddc46a5 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 15:58:29 -0500 Subject: [PATCH 09/52] Created Postgres service. --- __mocks__/fs.ts | 9 ++ src/services/database/__mocks__/client.ts | 17 ++++ src/services/database/client.spec.ts | 11 +++ src/services/database/client.ts | 20 +++++ src/services/database/migration.spec.ts | 105 ++++++++++++++++++++++ src/services/database/migration.ts | 76 ++++++++++++++++ 6 files changed, 238 insertions(+) create mode 100644 __mocks__/fs.ts create mode 100644 src/services/database/__mocks__/client.ts create mode 100644 src/services/database/client.spec.ts create mode 100644 src/services/database/client.ts create mode 100644 src/services/database/migration.spec.ts create mode 100644 src/services/database/migration.ts diff --git a/__mocks__/fs.ts b/__mocks__/fs.ts new file mode 100644 index 0000000..36131ad --- /dev/null +++ b/__mocks__/fs.ts @@ -0,0 +1,9 @@ +export const promises = { + stat: jest.fn(), + readdir: jest.fn(), + readFile: jest.fn() +}; + +export default { + promises +}; diff --git a/src/services/database/__mocks__/client.ts b/src/services/database/__mocks__/client.ts new file mode 100644 index 0000000..069016d --- /dev/null +++ b/src/services/database/__mocks__/client.ts @@ -0,0 +1,17 @@ +const txMock = { + manyOrNone: jest.fn().mockImplementation(() => Promise.resolve([])), + one: jest.fn().mockImplementation(() => Promise.resolve(null)), + any: jest.fn().mockImplementation(() => Promise.resolve()), + none: jest.fn().mockImplementation(() => Promise.resolve()), + multi: jest.fn().mockImplementation(() => Promise.resolve()) +}; + +export const db = { + _txMock: txMock, + manyOrNone: jest.fn().mockImplementation(() => Promise.resolve([])), + one: jest.fn().mockImplementation(() => Promise.resolve(null)), + any: jest.fn().mockImplementation(() => Promise.resolve()), + none: jest.fn().mockImplementation(() => Promise.resolve()), + multi: jest.fn().mockImplementation(() => Promise.resolve()), + tx: jest.fn().mockImplementation((fn) => fn(txMock)) +}; diff --git a/src/services/database/client.spec.ts b/src/services/database/client.spec.ts new file mode 100644 index 0000000..15f9b01 --- /dev/null +++ b/src/services/database/client.spec.ts @@ -0,0 +1,11 @@ +import { db } from './client'; + +jest.unmock('./client'); + +describe('Services > Database > Client', () => { + describe('db', () => { + it('should be defined.', () => { + expect(db).toBeDefined(); + }); + }); +}); diff --git a/src/services/database/client.ts b/src/services/database/client.ts new file mode 100644 index 0000000..f8137c4 --- /dev/null +++ b/src/services/database/client.ts @@ -0,0 +1,20 @@ +import pgPromise from 'pg-promise'; +import { + POSTGRES_HOST, + POSTGRES_PORT, + POSTGRES_DATABASE, + POSTGRES_USER, + POSTGRES_PASSWORD, + POSTGRES_MAX_SHARD_CONNECTIONS +} from '../../config/app'; + +const pgp = pgPromise(); + +export const db = pgp({ + host: POSTGRES_HOST, + port: POSTGRES_PORT, + database: POSTGRES_DATABASE, + user: POSTGRES_USER, + password: POSTGRES_PASSWORD, + max: POSTGRES_MAX_SHARD_CONNECTIONS +}); diff --git a/src/services/database/migration.spec.ts b/src/services/database/migration.spec.ts new file mode 100644 index 0000000..8db42e8 --- /dev/null +++ b/src/services/database/migration.spec.ts @@ -0,0 +1,105 @@ +/* eslint-disable no-underscore-dangle */ +import { runMigrations, runMigrationsForApp } from './migration'; +import * as thisModule from './migration'; +import fs from 'fs'; +import logger from '@moonstar-x/logger'; +import { db } from './client'; + +jest.mock('@moonstar-x/logger'); +jest.mock('fs'); +jest.mock('./client'); + +describe('Services > Database > Migration', () => { + const txMock = (db as unknown as { _txMock: Record<string, jest.Mock> })._txMock; + + describe('runMigrations()', () => { + beforeAll(() => { + (fs.promises.stat as jest.Mock).mockResolvedValue({ isDirectory: () => true }); + (fs.promises.readdir as jest.Mock).mockResolvedValue(['migration-1.sql', 'other.js', 'new-migration-2.sql']); + (fs.promises.readFile as jest.Mock).mockResolvedValue(''); + }); + + beforeEach(() => { + (db.tx as jest.Mock).mockClear(); + (txMock.one as jest.Mock).mockClear(); + (txMock.multi as jest.Mock).mockClear(); + }); + + afterAll(() => { + (fs.promises.stat as jest.Mock).mockReset(); + (fs.promises.readdir as jest.Mock).mockReset(); + (fs.promises.readFile as jest.Mock).mockReset(); + }); + + it('should be defined.', () => { + expect(runMigrations).toBeDefined(); + }); + + it('should reject an error if directory is not a valid directory.', () => { + (fs.promises.stat as jest.Mock).mockResolvedValueOnce({ isDirectory: () => false }); + expect(runMigrations('')).rejects.toThrow('Migrations directory provided does not exist or is not a directory.'); + }); + + it('should reject an error if directory is empty.', () => { + (fs.promises.readdir as jest.Mock).mockResolvedValueOnce([]); + expect(runMigrations('')).rejects.toThrow('Migrations directory is empty.'); + }); + + it('should create the migrations table.', async () => { + await runMigrations(''); + expect(db.tx).toHaveBeenCalled(); + expect(txMock.any).toHaveBeenCalledWith('CREATE TABLE IF NOT EXISTS __migrations(id SERIAL PRIMARY KEY, name VARCHAR(256) NOT NULL UNIQUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now())'); + }); + + it('should only apply new migrations.', async () => { + (db.manyOrNone as jest.Mock).mockResolvedValueOnce([{ name: 'migration-1.sql' }]); + (fs.promises.readFile as jest.Mock).mockResolvedValueOnce('my query'); + + await runMigrations(''); + expect(db.tx).toHaveBeenCalled(); + expect(txMock.multi).toHaveBeenCalledWith('my query'); + expect(txMock.one).toHaveBeenCalledWith('INSERT INTO __migrations(name) VALUES($1) RETURNING *', ['new-migration-2.sql']); + }); + + it('should resolve an array of the new migrations.', async () => { + (db.manyOrNone as jest.Mock).mockResolvedValueOnce([{ name: 'migration-1.sql' }]); + (txMock.one as jest.Mock).mockResolvedValue({ name: 'new-migration-2.sql' }); + + expect(await runMigrations('')).toStrictEqual([{ name: 'new-migration-2.sql' }]); + + (txMock.one as jest.Mock).mockReset(); + }); + }); + + describe('runMigrationsForApp()', () => { + let runMigrationsSpy: jest.SpyInstance; + + beforeAll(() => { + runMigrationsSpy = jest.spyOn(thisModule, 'runMigrations', undefined as never); + runMigrationsSpy.mockResolvedValue([]); + }); + + afterAll(() => { + runMigrationsSpy.mockRestore(); + }); + + it('should be defined.', () => { + expect(runMigrationsForApp).toBeDefined(); + }); + + it('should log the migrations.', async () => { + runMigrationsSpy.mockResolvedValueOnce([{ name: 'migration-1.sql' }, { name: 'migration-2.sql' }]); + await runMigrationsForApp(''); + + expect(logger.info).toHaveBeenCalledWith('New migration applied: migration-1.sql'); + expect(logger.info).toHaveBeenCalledWith('New migration applied: migration-2.sql'); + }); + + it('should return the migrations.', async () => { + const migrations = [{ name: 'migration-1.sql' }, { name: 'migration-2.sql' }]; + runMigrationsSpy.mockResolvedValueOnce(migrations); + + expect(await runMigrationsForApp('')).toBe(migrations); + }); + }); +}); diff --git a/src/services/database/migration.ts b/src/services/database/migration.ts new file mode 100644 index 0000000..3044c27 --- /dev/null +++ b/src/services/database/migration.ts @@ -0,0 +1,76 @@ +import fs from 'fs'; +import path from 'path'; +import logger from '@moonstar-x/logger'; +import { db } from './client'; + +export interface Migration { + id: number + name: string + created_at: Date +} + +const getMigrationFiles = async (directory: string): Promise<string[]> => { + const directoryStat = await fs.promises.stat(directory); + if (!directoryStat.isDirectory()) { + throw new Error('Migrations directory provided does not exist or is not a directory.'); + } + + const files = await fs.promises.readdir(directory); + return files.filter((file) => file.endsWith('.sql')); +}; + +const initializeMigrationTable = async () => { + await db.tx(async (t) => { + const statement = `CREATE TABLE IF NOT EXISTS __migrations(id SERIAL PRIMARY KEY, name VARCHAR(256) NOT NULL UNIQUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now())`; + await t.any(statement); + }); +}; + +const getNewMigrations = async (migrations: string[]): Promise<string[]> => { + const query = 'SELECT * FROM __migrations'; + const storedMigrations = await db.manyOrNone<Migration>(query); + + const storedMigrationsSet = new Set(storedMigrations.map((migration) => migration.name)); + + return migrations.filter((migration) => !storedMigrationsSet.has(migration)); +}; + +const applyMigrations = async (migrationsDirectory: string, migrations: string[]): Promise<Migration[]> => { + return await db.tx(async (t) => { + return await Promise.all(migrations.map(async (migrationName) => { + const migrationFilename = path.join(migrationsDirectory, migrationName); + const migrationStatement = await fs.promises.readFile(migrationFilename, { encoding: 'utf-8' }); + await t.multi(migrationStatement); + + const query = 'INSERT INTO __migrations(name) VALUES($1) RETURNING *'; + return await t.one<Migration>(query, [migrationName]); + })); + }); +}; + +export const runMigrations = async (migrationsDirectory: string): Promise<Migration[]> => { + const migrations = await getMigrationFiles(migrationsDirectory); + + if (!migrations.length) { + throw new Error('Migrations directory is empty.'); + } + + await initializeMigrationTable(); + const newMigrations = await getNewMigrations(migrations); + + if (!newMigrations.length) { + return []; + } + + return applyMigrations(migrationsDirectory, newMigrations); +}; + +export const runMigrationsForApp = async (migrationsDirectory: string): Promise<Migration[]> => { + const newMigrations = await runMigrations(migrationsDirectory); + + newMigrations.forEach((migration) => { + logger.info(`New migration applied: ${migration.name}`); + }); + + return newMigrations; +}; From dc676d74fd6e82de4c9fe5527527a6bb0a7c0ad8 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 16:17:58 -0500 Subject: [PATCH 10/52] Migrations are now executed on start and startSharded. --- src/app/migration.spec.ts | 27 +++++++++++++++++++++++++++ src/app/migration.ts | 7 +++++++ src/entrypoint/start.spec.ts | 12 ++++++++++++ src/entrypoint/start.ts | 6 +++++- src/entrypoint/startSharded.spec.ts | 12 ++++++++++++ src/entrypoint/startSharded.ts | 6 +++++- 6 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/app/migration.spec.ts create mode 100644 src/app/migration.ts diff --git a/src/app/migration.spec.ts b/src/app/migration.spec.ts new file mode 100644 index 0000000..27c057e --- /dev/null +++ b/src/app/migration.spec.ts @@ -0,0 +1,27 @@ +import { runMigrations } from './migration'; +import { runMigrationsForApp } from '../services/database/migration'; + +jest.mock('../services/database/migration', () => { + return { + runMigrationsForApp: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + +describe('App > Migration', () => { + describe('runMigrations()', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(runMigrations).toBeDefined(); + }); + + it('should call runMigrations with correct path.', async () => { + await runMigrations(); + + const call = (runMigrationsForApp as jest.Mock).mock.calls[0]; + expect(call[0]).toMatch(/\/migrations$/); + }); + }); +}); diff --git a/src/app/migration.ts b/src/app/migration.ts new file mode 100644 index 0000000..4f67a8f --- /dev/null +++ b/src/app/migration.ts @@ -0,0 +1,7 @@ +import path from 'path'; +import { runMigrationsForApp } from '../services/database/migration'; + +export const runMigrations = () => { + const migrationsDirectory = path.join(__dirname, '../../migrations'); + return runMigrationsForApp(migrationsDirectory); +}; diff --git a/src/entrypoint/start.spec.ts b/src/entrypoint/start.spec.ts index f9d2821..7fc5add 100644 --- a/src/entrypoint/start.spec.ts +++ b/src/entrypoint/start.spec.ts @@ -1,4 +1,5 @@ import { ExtendedClient } from '../base/client/ExtendedClient'; +import { runMigrations } from '../app/migration'; const client = { login: jest.fn() @@ -10,6 +11,12 @@ jest.mock('../app/client', () => { }; }); +jest.mock('../app/migration', () => { + return { + runMigrations: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + jest.mock('../config/app', () => { return { DISCORD_TOKEN: 'token' @@ -28,6 +35,11 @@ describe('Entrypoint > Start', () => { jest.clearAllMocks(); }); + it('should call migrations.', async () => { + await load(); + expect(runMigrations).toHaveBeenCalled(); + }); + it('should login client with token.', async () => { await load(); expect(client.login).toHaveBeenCalledWith('token'); diff --git a/src/entrypoint/start.ts b/src/entrypoint/start.ts index 6043b25..bed2da3 100644 --- a/src/entrypoint/start.ts +++ b/src/entrypoint/start.ts @@ -1,6 +1,10 @@ import { createClient } from '../app/client'; import { DISCORD_TOKEN } from '../config/app'; +import { runMigrations } from '../app/migration'; const client = createClient(); -client.login(DISCORD_TOKEN); +runMigrations() + .then(() => { + client.login(DISCORD_TOKEN); + }); diff --git a/src/entrypoint/startSharded.spec.ts b/src/entrypoint/startSharded.spec.ts index ad2900d..c2e576e 100644 --- a/src/entrypoint/startSharded.spec.ts +++ b/src/entrypoint/startSharded.spec.ts @@ -1,6 +1,7 @@ import { ShardingManager } from 'discord.js'; import EventEmitter from 'events'; import logger from '@moonstar-x/logger'; +import { runMigrations } from '../app/migration'; const manager = new EventEmitter() as unknown as ShardingManager; manager.spawn = jest.fn(); @@ -11,6 +12,12 @@ jest.mock('../config/app', () => { }; }); +jest.mock('../app/migration', () => { + return { + runMigrations: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + jest.mock('discord.js', () => { return { ShardingManager: jest.fn().mockReturnValue(manager) @@ -31,6 +38,11 @@ describe('Entrypoint > Start Sharded', () => { jest.clearAllMocks(); }); + it('should call migrations.', async () => { + await load(); + expect(runMigrations).toHaveBeenCalled(); + }); + it('should spawn manager.', async () => { await load(); expect(manager.spawn).toHaveBeenCalledWith({ amount: 'auto' }); diff --git a/src/entrypoint/startSharded.ts b/src/entrypoint/startSharded.ts index 8270746..47aad63 100644 --- a/src/entrypoint/startSharded.ts +++ b/src/entrypoint/startSharded.ts @@ -2,6 +2,7 @@ import path from 'path'; import { ShardingManager } from 'discord.js'; import logger from '@moonstar-x/logger'; import { DISCORD_TOKEN } from '../config/app'; +import { runMigrations } from '../app/migration'; // Note: This works only when built, not in TypeScript. const startScript = path.join(__dirname, `./start.js`); @@ -12,4 +13,7 @@ manager.on('shardCreate', (shard) => { logger.info(`Launched shard with ID: ${shard.id}.`); }); -manager.spawn({ amount: 'auto' }); +runMigrations() + .then(() => { + manager.spawn({ amount: 'auto' }); + }); From f344084ecbef79bd1ae6f90ac19b1ab335dfc26d Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 18:53:45 -0500 Subject: [PATCH 11/52] Created database migration. --- migrations/001-init.sql | 120 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 migrations/001-init.sql diff --git a/migrations/001-init.sql b/migrations/001-init.sql new file mode 100644 index 0000000..617b29c --- /dev/null +++ b/migrations/001-init.sql @@ -0,0 +1,120 @@ +CREATE TABLE IF NOT EXISTS GuildSettings( + guild VARCHAR(64) NOT NULL, + channel VARCHAR(64), + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT pk_GuildSettings + PRIMARY KEY (guild) +); + +CREATE TABLE IF NOT EXISTS GameOfferStorefront( + id INT NOT NULL, + name VARCHAR(256) UNIQUE NOT NULL, + CONSTRAINT pk_GameOfferStorefront + PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS idx_GameOfferStorefront ON GameOfferStorefront(name); + +CREATE TABLE IF NOT EXISTS GuildGameOffersEnabled( + guild VARCHAR(64) NOT NULL, + storefront_id INTEGER NOT NULL, + enabled BOOL NOT NULL DEFAULT TRUE, + CONSTRAINT pk_GuildGameOffersEnabled + PRIMARY KEY (guild, storefront_id), + CONSTRAINT fk_GuildGameOffersEnabled_GuildSettings + FOREIGN KEY (guild) REFERENCES GuildSettings(guild) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT fk_GuildGameOffersEnabled_GameOfferStorefront + FOREIGN KEY (storefront_id) REFERENCES GameOfferStorefront(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +INSERT INTO GameOfferStorefront(id, name) VALUES(1, 'EpicGames') ON CONFLICT DO NOTHING; +INSERT INTO GameOfferStorefront(id, name) VALUES(2, 'Steam') ON CONFLICT DO NOTHING; + +CREATE OR REPLACE FUNCTION get_storefronts() +RETURNS JSON AS $$ +BEGIN + RETURN ( + SELECT json_agg(name) FROM GameOfferStorefront + ); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION get_guild(guild_id VARCHAR) +RETURNS JSON AS $$ +BEGIN + RETURN ( + SELECT json_build_object( + 'guild', GS.guild, + 'channel', GS.channel, + 'created_at', GS.created_at, + 'updated_at', GS.updated_at, + 'storefronts', ( + SELECT json_object_agg( + GOS.name, + json_build_object( + 'enabled', COALESCE(GGOE.enabled, true) + ) + ) + FROM ( + SELECT GOS.id, GOS.name FROM GameOfferStorefront GOS + LEFT JOIN GuildGameOffersEnabled GGOE ON GOS.id = GGOE.storefront_id AND GGOE.guild = guild_id + ) GOS + LEFT JOIN GuildGameOffersEnabled GGOE ON GOS.id = GGOE.storefront_id AND GGOE.guild = guild_id + ) + ) FROM GuildSettings GS + WHERE GS.guild = guild_id + ); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION update_or_create_guild_channel(guild_id VARCHAR, new_channel_id VARCHAR) +RETURNS VOID AS $$ +BEGIN + UPDATE GuildSettings + SET channel = new_channel_id, + updated_at = now() + WHERE guild = guild_id; + + IF NOT FOUND THEN + INSERT INTO GuildSettings(guild, channel) VALUES(guild_id, new_channel_id); + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION set_guild_game_offer_enabled(guild_id VARCHAR, storefront_name VARCHAR, new_enabled BOOL) +RETURNS VOID AS $$ +DECLARE + v_storefront_id INT; +BEGIN + SELECT id INTO v_storefront_id FROM GameOfferStorefront + WHERE name = storefront_name; + + IF v_storefront_id IS NULL THEN + RAISE EXCEPTION 'Storefront name % not found.', storefront_name; + END IF; + + UPDATE GuildSettings + SET updated_at = now() + WHERE guild = guild_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'Guild % not found.', guild_id; + END IF; + + INSERT INTO GuildGameOffersEnabled(guild, storefront_id, enabled) VALUES(guild_id, v_storefront_id, new_enabled) + ON CONFLICT (guild, storefront_id) DO UPDATE + SET enabled = EXCLUDED.enabled; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION delete_guild(guild_id VARCHAR) +RETURNS VOID AS $$ +BEGIN + DELETE FROM GuildSettings WHERE guild = guild_id; +END; +$$ LANGUAGE plpgsql; From b0d4bac65ef08533f813c906fa7f2119388558d2 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 19:07:01 -0500 Subject: [PATCH 12/52] Created Redis client. --- __mocks__/redis.ts | 8 +++++ src/services/redis/__mocks__/client.ts | 1 + src/services/redis/client.spec.ts | 44 ++++++++++++++++++++++++++ src/services/redis/client.ts | 18 +++++++++++ 4 files changed, 71 insertions(+) create mode 100644 __mocks__/redis.ts create mode 100644 src/services/redis/__mocks__/client.ts create mode 100644 src/services/redis/client.spec.ts create mode 100644 src/services/redis/client.ts diff --git a/__mocks__/redis.ts b/__mocks__/redis.ts new file mode 100644 index 0000000..3125375 --- /dev/null +++ b/__mocks__/redis.ts @@ -0,0 +1,8 @@ +export const createClient = jest.fn().mockReturnValue({ + connect: jest.fn(), + disconnect: jest.fn() +}); + +export default { + createClient +}; diff --git a/src/services/redis/__mocks__/client.ts b/src/services/redis/__mocks__/client.ts new file mode 100644 index 0000000..9f75a71 --- /dev/null +++ b/src/services/redis/__mocks__/client.ts @@ -0,0 +1 @@ +export const createRedisClient = jest.fn(); diff --git a/src/services/redis/client.spec.ts b/src/services/redis/client.spec.ts new file mode 100644 index 0000000..54074cd --- /dev/null +++ b/src/services/redis/client.spec.ts @@ -0,0 +1,44 @@ +import { createRedisClient, withRedis } from './client'; +import redis from 'redis'; + +jest.unmock('./client'); + +jest.mock('redis'); + +jest.mock('../../config/app', () => { + return { + REDIS_URI: 'redis://localhost:6379' + }; +}); + +describe('Services > Redis > Client', () => { + describe('createRedisClient()', () => { + it('should be defined.', () => { + expect(createRedisClient).toBeDefined(); + }); + + it('should return a Redis client with the correct URI.', () => { + createRedisClient(); + expect(redis.createClient).toHaveBeenCalledWith({ url: 'redis://localhost:6379' }); + }); + }); + + describe('withRedis()', () => { + it('should be defined.', () => { + expect(withRedis).toBeDefined(); + }); + + it('should call client connect and disconnect.', async () => { + const client = createRedisClient(); + await withRedis(() => Promise.resolve()); + + expect(client.connect).toHaveBeenCalled(); + expect(client.disconnect).toHaveBeenCalled(); + }); + + it('should return the function result.', async () => { + const result = await withRedis(() => Promise.resolve(123)); + expect(result).toBe(123); + }); + }); +}); diff --git a/src/services/redis/client.ts b/src/services/redis/client.ts new file mode 100644 index 0000000..f39a097 --- /dev/null +++ b/src/services/redis/client.ts @@ -0,0 +1,18 @@ +import { createClient } from 'redis'; +import { REDIS_URI } from '../../config/app'; + +export const createRedisClient = () => { + return createClient({ url: REDIS_URI }); +}; + +export type WithRedisFunction<TResult = unknown> = (client: ReturnType<typeof createRedisClient>) => Promise<TResult>; + +export const withRedis = async <TResult = unknown>(fn: WithRedisFunction<TResult>) => { + const client = createRedisClient(); + await client.connect(); + + const result = await fn(client); + + await client.disconnect(); + return result; +}; From 5c2d2505f3eecc11544a5b6a8e83c8857862cb0a Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 2 Aug 2024 20:14:01 -0500 Subject: [PATCH 13/52] Added getCurrentGameOffers --- .eslintrc | 3 +- src/commands/test/TestCommand.ts | 8 +++- .../functions/getCurrentGameOffers.spec.ts | 46 +++++++++++++++++++ .../functions/getCurrentGameOffers.ts | 13 ++++++ src/models/{offer.ts => gameOffer.ts} | 0 src/services/redis/__mocks__/client.ts | 11 ++++- 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/features/gameOffers/functions/getCurrentGameOffers.spec.ts create mode 100644 src/features/gameOffers/functions/getCurrentGameOffers.ts rename src/models/{offer.ts => gameOffer.ts} (100%) diff --git a/.eslintrc b/.eslintrc index 392f623..d6d56ed 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,6 +4,7 @@ "@moonstar-x/eslint-config/rules/typescript" ], "rules": { - "no-dupe-class-members": "off" + "no-dupe-class-members": "off", + "camelcase": "off" } } \ No newline at end of file diff --git a/src/commands/test/TestCommand.ts b/src/commands/test/TestCommand.ts index 5646eaa..9a62a65 100644 --- a/src/commands/test/TestCommand.ts +++ b/src/commands/test/TestCommand.ts @@ -1,6 +1,7 @@ import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; import { Command } from '../../base/command/Command'; import { ExtendedClient } from '../../base/client/ExtendedClient'; +import { getCurrentGameOffers } from '../../features/gameOffers/functions/getCurrentGameOffers'; export default class TestCommand extends Command { public constructor(client: ExtendedClient) { @@ -14,6 +15,11 @@ export default class TestCommand extends Command { } public override async run(interaction: ChatInputCommandInteraction): Promise<void> { - await interaction.reply({ content: 'Hi!' }); + const result = await getCurrentGameOffers(); + await interaction.reply({ content: ` + \`\`\` +${JSON.stringify(result, null, 2)} + \`\`\` + ` }); } } diff --git a/src/features/gameOffers/functions/getCurrentGameOffers.spec.ts b/src/features/gameOffers/functions/getCurrentGameOffers.spec.ts new file mode 100644 index 0000000..6f4961a --- /dev/null +++ b/src/features/gameOffers/functions/getCurrentGameOffers.spec.ts @@ -0,0 +1,46 @@ +import { getCurrentGameOffers } from './getCurrentGameOffers'; +import { createRedisClient } from '../../../services/redis/client'; + +jest.mock('../../../services/redis/client'); + +describe('Features > GameOffers > Functions > GetCurrentGameOffers', () => { + const client = createRedisClient(); + + const gameOffer = { + storefront: 'Steam', + id: '442070', + url: 'https://store.steampowered.com/app/442070/Drawful_2/?snr=1_7_7_2300_150_1', + title: 'Drawful 2', + description: 'For 3-8 players and an audience of thousands! Your phones or tablets are your controllers! The game of terrible drawings and hilariously wrong answers.', + type: 'game', + publisher: 'Jackbox Games', + original_price: 5.79, + original_price_fmt: '$5.79 USD', + thumbnail: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/442070/header.jpg?t=1721927113' + }; + + beforeAll(() => { + (client.keys as jest.Mock).mockReturnValue([1, 2]); + (client.mGet as jest.Mock).mockReturnValue([JSON.stringify(gameOffer), null]); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + (client.keys as jest.Mock).mockReset(); + (client.mGet as jest.Mock).mockReset(); + }); + + describe('getCurrentGameOffers()', () => { + it('should be defined.', () => { + expect(getCurrentGameOffers).toBeDefined(); + }); + + it('should return game offers without null.', async () => { + const result = await getCurrentGameOffers(); + expect(result).toStrictEqual([gameOffer]); + }); + }); +}); diff --git a/src/features/gameOffers/functions/getCurrentGameOffers.ts b/src/features/gameOffers/functions/getCurrentGameOffers.ts new file mode 100644 index 0000000..0388e4a --- /dev/null +++ b/src/features/gameOffers/functions/getCurrentGameOffers.ts @@ -0,0 +1,13 @@ +import { withRedis } from '../../../services/redis/client'; +import { GameOffer } from '../../../models/gameOffer'; + +export const getCurrentGameOffers = (): Promise<GameOffer[]> => { + return withRedis(async (client) => { + const keys = await client.keys('offer:*'); + const items = await client.mGet(keys); + + return items + .filter((item) => item !== null) + .map((item: string) => JSON.parse(item)); + }); +}; diff --git a/src/models/offer.ts b/src/models/gameOffer.ts similarity index 100% rename from src/models/offer.ts rename to src/models/gameOffer.ts diff --git a/src/services/redis/__mocks__/client.ts b/src/services/redis/__mocks__/client.ts index 9f75a71..d65a061 100644 --- a/src/services/redis/__mocks__/client.ts +++ b/src/services/redis/__mocks__/client.ts @@ -1 +1,10 @@ -export const createRedisClient = jest.fn(); +const client = { + connect: jest.fn(), + disconnect: jest.fn(), + keys: jest.fn(), + mGet: jest.fn() +}; + +export const createRedisClient = jest.fn().mockReturnValue(client); + +export const withRedis = jest.fn().mockImplementation((fn) => fn(client)); From 440d4c85db3e59f846f863def91477e699670900 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Sun, 4 Aug 2024 09:57:50 -0500 Subject: [PATCH 14/52] Implemented getStorefronts. --- .../functions/getStorefronts.spec.ts | 25 +++++++++++++++++++ .../gameOffers/functions/getStorefronts.ts | 8 ++++++ 2 files changed, 33 insertions(+) create mode 100644 src/features/gameOffers/functions/getStorefronts.spec.ts create mode 100644 src/features/gameOffers/functions/getStorefronts.ts diff --git a/src/features/gameOffers/functions/getStorefronts.spec.ts b/src/features/gameOffers/functions/getStorefronts.spec.ts new file mode 100644 index 0000000..e421e1f --- /dev/null +++ b/src/features/gameOffers/functions/getStorefronts.spec.ts @@ -0,0 +1,25 @@ +import { getStorefronts } from './getStorefronts'; +import { db } from '../../../services/database/client'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > GetStorefronts', () => { + const storefronts = ['EpicGames', 'Steam']; + + beforeAll(() => { + (db.one as jest.Mock).mockResolvedValue({ result: storefronts }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(getStorefronts).toBeDefined(); + }); + + it('should return the result.', async () => { + const result = await getStorefronts(); + expect(result).toBe(storefronts); + }); +}); diff --git a/src/features/gameOffers/functions/getStorefronts.ts b/src/features/gameOffers/functions/getStorefronts.ts new file mode 100644 index 0000000..4382b81 --- /dev/null +++ b/src/features/gameOffers/functions/getStorefronts.ts @@ -0,0 +1,8 @@ +import { db } from '../../../services/database/client'; + +export const getStorefronts = async (): Promise<string[]> => { + const query = 'SELECT get_storefronts() AS result'; + const result = await db.one<{ result: string[] }>(query); + + return result.result; +}; From 6ea911f9d7698cc709309ccb050d72348ef48082 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Sun, 4 Aug 2024 10:31:02 -0500 Subject: [PATCH 15/52] Implemented getGuild service. --- migrations/001-init.sql | 4 +- .../gameOffers/functions/getGuild.spec.ts | 46 +++++++++++++++++++ src/features/gameOffers/functions/getGuild.ts | 9 ++++ .../functions/getStorefronts.spec.ts | 7 +++ .../gameOffers/functions/getStorefronts.ts | 2 +- src/models/gameOffer.ts | 12 +++++ 6 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/features/gameOffers/functions/getGuild.spec.ts create mode 100644 src/features/gameOffers/functions/getGuild.ts diff --git a/migrations/001-init.sql b/migrations/001-init.sql index 617b29c..56ad0e9 100644 --- a/migrations/001-init.sql +++ b/migrations/001-init.sql @@ -54,12 +54,12 @@ BEGIN 'created_at', GS.created_at, 'updated_at', GS.updated_at, 'storefronts', ( - SELECT json_object_agg( + SELECT COALESCE(json_object_agg( GOS.name, json_build_object( 'enabled', COALESCE(GGOE.enabled, true) ) - ) + ), '{}'::json) FROM ( SELECT GOS.id, GOS.name FROM GameOfferStorefront GOS LEFT JOIN GuildGameOffersEnabled GGOE ON GOS.id = GGOE.storefront_id AND GGOE.guild = guild_id diff --git a/src/features/gameOffers/functions/getGuild.spec.ts b/src/features/gameOffers/functions/getGuild.spec.ts new file mode 100644 index 0000000..d31cd44 --- /dev/null +++ b/src/features/gameOffers/functions/getGuild.spec.ts @@ -0,0 +1,46 @@ +import { getGuild } from './getGuild'; +import { db } from '../../../services/database/client'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > GetGuild', () => { + const guildId = '1267881983548063785'; + const guildData = { + guild: '1267881983548063785', + channel: null, + created_at: '2024-08-04T15:16:40.054841+00:00', + updated_at: '2024-08-04T15:16:40.054841+00:00', + storefronts: { + EpicGames: { + enabled: true + }, + Steam: { + enabled: true + } + } + }; + + beforeAll(() => { + (db.one as jest.Mock).mockResolvedValue({ result: guildData }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(getGuild).toBeDefined(); + }); + + it('should return the result.', async () => { + const result = await getGuild(guildId); + expect(result).toBe(guildData); + }); + + it('should return null if not found result.', async () => { + (db.one as jest.Mock).mockResolvedValueOnce({ result: null }); + const result = await getGuild(guildId); + + expect(result).toBeNull(); + }); +}); diff --git a/src/features/gameOffers/functions/getGuild.ts b/src/features/gameOffers/functions/getGuild.ts new file mode 100644 index 0000000..287ac51 --- /dev/null +++ b/src/features/gameOffers/functions/getGuild.ts @@ -0,0 +1,9 @@ +import { db } from '../../../services/database/client'; +import { GameOfferGuild } from '../../../models/gameOffer'; + +export const getGuild = async (guildId: string): Promise<GameOfferGuild | null> => { + const query = 'SELECT get_guild($1) AS result'; + const result = await db.one<{ result: GameOfferGuild | null }>(query, [guildId]); + + return result.result; +}; diff --git a/src/features/gameOffers/functions/getStorefronts.spec.ts b/src/features/gameOffers/functions/getStorefronts.spec.ts index e421e1f..c6eae6c 100644 --- a/src/features/gameOffers/functions/getStorefronts.spec.ts +++ b/src/features/gameOffers/functions/getStorefronts.spec.ts @@ -22,4 +22,11 @@ describe('Features > GameOffers > Functions > GetStorefronts', () => { const result = await getStorefronts(); expect(result).toBe(storefronts); }); + + it('should return empty array if not found result.', async () => { + (db.one as jest.Mock).mockResolvedValueOnce({ result: null }); + const result = await getStorefronts(); + + expect(result).toStrictEqual([]); + }); }); diff --git a/src/features/gameOffers/functions/getStorefronts.ts b/src/features/gameOffers/functions/getStorefronts.ts index 4382b81..d612f07 100644 --- a/src/features/gameOffers/functions/getStorefronts.ts +++ b/src/features/gameOffers/functions/getStorefronts.ts @@ -4,5 +4,5 @@ export const getStorefronts = async (): Promise<string[]> => { const query = 'SELECT get_storefronts() AS result'; const result = await db.one<{ result: string[] }>(query); - return result.result; + return result.result ?? []; }; diff --git a/src/models/gameOffer.ts b/src/models/gameOffer.ts index 3aa9781..c1e0350 100644 --- a/src/models/gameOffer.ts +++ b/src/models/gameOffer.ts @@ -12,3 +12,15 @@ export interface GameOffer { original_price_fmt: string | null thumbnail: string | null } + +export interface GameOfferGuild { + guild: string + channel: string | null + created_at: string + updated_at: string + storefronts: { + [k: string]: { + enabled: boolean + } + } +} From be86349c95e182cff3e8ad77a69433a6085c6f27 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Sun, 4 Aug 2024 11:32:50 -0500 Subject: [PATCH 16/52] Implemented updateOrCreateGuildChannel. --- src/base/command/Command.ts | 6 ++--- .../updateOrCreateGuildChannel.spec.ts | 26 +++++++++++++++++++ .../functions/updateOrCreateGuildChannel.ts | 6 +++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/features/gameOffers/functions/updateOrCreateGuildChannel.spec.ts create mode 100644 src/features/gameOffers/functions/updateOrCreateGuildChannel.ts diff --git a/src/base/command/Command.ts b/src/base/command/Command.ts index df549ff..19a1ca1 100644 --- a/src/base/command/Command.ts +++ b/src/base/command/Command.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder, PermissionResolvable, DMChannel, PermissionsBitField } from 'discord.js'; +import { ChatInputCommandInteraction, SlashCommandBuilder, PermissionResolvable, DMChannel, PermissionsBitField, SlashCommandOptionsOnlyBuilder } from 'discord.js'; import { ExtendedClient } from '../client/ExtendedClient'; export interface CommandOptions { @@ -7,7 +7,7 @@ export interface CommandOptions { emoji?: string guildOnly: boolean permissions?: PermissionResolvable | null - builder: SlashCommandBuilder + builder: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder } export abstract class Command { @@ -18,7 +18,7 @@ export abstract class Command { public readonly emoji: string; public readonly guildOnly: boolean; public readonly permissions: PermissionResolvable | null; - public readonly builder: SlashCommandBuilder; + public readonly builder: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder; protected constructor(client: ExtendedClient, options: CommandOptions) { this.client = client; diff --git a/src/features/gameOffers/functions/updateOrCreateGuildChannel.spec.ts b/src/features/gameOffers/functions/updateOrCreateGuildChannel.spec.ts new file mode 100644 index 0000000..e72c252 --- /dev/null +++ b/src/features/gameOffers/functions/updateOrCreateGuildChannel.spec.ts @@ -0,0 +1,26 @@ +import { updateOrCreateGuildChannel } from './updateOrCreateGuildChannel'; +import { db } from '../../../services/database/client'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > UpdateOrCreateGuildChannel', () => { + const guildId = '1267881983548063785'; + const channelId = '1267881984642908346'; + + beforeAll(() => { + (db.any as jest.Mock).mockImplementation(() => Promise.resolve()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(updateOrCreateGuildChannel).toBeDefined(); + }); + + it('should return undefined.', async () => { + const result = await updateOrCreateGuildChannel(guildId, channelId); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/features/gameOffers/functions/updateOrCreateGuildChannel.ts b/src/features/gameOffers/functions/updateOrCreateGuildChannel.ts new file mode 100644 index 0000000..6a730b7 --- /dev/null +++ b/src/features/gameOffers/functions/updateOrCreateGuildChannel.ts @@ -0,0 +1,6 @@ +import { db } from '../../../services/database/client'; + +export const updateOrCreateGuildChannel = async (guildId: string, channelId: string): Promise<void> => { + const query = 'SELECT update_or_create_guild_channel($1, $2)'; + await db.any(query, [guildId, channelId]); +}; From 809bf92eec5bfb3b24afea7b058b2c0d7f60c0bf Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 11:50:33 -0500 Subject: [PATCH 17/52] Implemented SetGuildGameOffersEnabled. --- .../functions/setGuildGameOfferEnabled.ts | 6 +++++ .../setGuildGameOffersEnabled.spec.ts | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/features/gameOffers/functions/setGuildGameOfferEnabled.ts create mode 100644 src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts diff --git a/src/features/gameOffers/functions/setGuildGameOfferEnabled.ts b/src/features/gameOffers/functions/setGuildGameOfferEnabled.ts new file mode 100644 index 0000000..f2beb61 --- /dev/null +++ b/src/features/gameOffers/functions/setGuildGameOfferEnabled.ts @@ -0,0 +1,6 @@ +import { db } from '../../../services/database/client'; + +export const setGuildGameOfferEnabled = async (guildId: string, storefront: string, enabled: boolean): Promise<void> => { + const query = 'SELECT set_guild_game_offer_enabled($1, $2, $3) AS result'; + await db.any(query, [guildId, storefront, enabled]); +}; diff --git a/src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts b/src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts new file mode 100644 index 0000000..09a51c7 --- /dev/null +++ b/src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts @@ -0,0 +1,25 @@ +import { setGuildGameOfferEnabled } from './setGuildGameOfferEnabled'; +import { db } from '../../../services/database/client'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > SetGuildGameOffersEnabled', () => { + const guildId = '1267881983548063785'; + + beforeAll(() => { + (db.any as jest.Mock).mockImplementation(() => Promise.resolve()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(setGuildGameOfferEnabled).toBeDefined(); + }); + + it('should return undefined.', async () => { + const result = await setGuildGameOfferEnabled(guildId, 'EpicGames', true); + expect(result).toBeUndefined(); + }); +}); From 1eab7d8b8f7fad974e4d7a7b9327271dddab64a3 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 11:54:35 -0500 Subject: [PATCH 18/52] Implemented DeleteGuild. --- .../gameOffers/functions/deleteGuild.spec.ts | 25 +++++++++++++++++++ .../gameOffers/functions/deleteGuild.ts | 6 +++++ .../setGuildGameOfferEnabled.spec.ts | 25 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 src/features/gameOffers/functions/deleteGuild.spec.ts create mode 100644 src/features/gameOffers/functions/deleteGuild.ts create mode 100644 src/features/gameOffers/functions/setGuildGameOfferEnabled.spec.ts diff --git a/src/features/gameOffers/functions/deleteGuild.spec.ts b/src/features/gameOffers/functions/deleteGuild.spec.ts new file mode 100644 index 0000000..9d07ff9 --- /dev/null +++ b/src/features/gameOffers/functions/deleteGuild.spec.ts @@ -0,0 +1,25 @@ +import { deleteGuild } from './deleteGuild'; +import { db } from '../../../services/database/client'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > DeleteGuild', () => { + const guildId = '1267881983548063785'; + + beforeAll(() => { + (db.any as jest.Mock).mockImplementation(() => Promise.resolve()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(deleteGuild).toBeDefined(); + }); + + it('should return undefined.', async () => { + const result = await deleteGuild(guildId); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/features/gameOffers/functions/deleteGuild.ts b/src/features/gameOffers/functions/deleteGuild.ts new file mode 100644 index 0000000..f1f2b80 --- /dev/null +++ b/src/features/gameOffers/functions/deleteGuild.ts @@ -0,0 +1,6 @@ +import { db } from '../../../services/database/client'; + +export const deleteGuild = async (guildId: string): Promise<void> => { + const query = 'SELECT delete_guild($1)'; + await db.any(query, [guildId]); +}; diff --git a/src/features/gameOffers/functions/setGuildGameOfferEnabled.spec.ts b/src/features/gameOffers/functions/setGuildGameOfferEnabled.spec.ts new file mode 100644 index 0000000..09a51c7 --- /dev/null +++ b/src/features/gameOffers/functions/setGuildGameOfferEnabled.spec.ts @@ -0,0 +1,25 @@ +import { setGuildGameOfferEnabled } from './setGuildGameOfferEnabled'; +import { db } from '../../../services/database/client'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > SetGuildGameOffersEnabled', () => { + const guildId = '1267881983548063785'; + + beforeAll(() => { + (db.any as jest.Mock).mockImplementation(() => Promise.resolve()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(setGuildGameOfferEnabled).toBeDefined(); + }); + + it('should return undefined.', async () => { + const result = await setGuildGameOfferEnabled(guildId, 'EpicGames', true); + expect(result).toBeUndefined(); + }); +}); From a5d7ed483be1b5e78066e002a933fd20213a6042 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 12:05:31 -0500 Subject: [PATCH 19/52] Removed custom guildOnly and handlePermission from Command base. --- src/base/command/Command.spec.ts | 106 +----------------- src/base/command/Command.ts | 35 +----- src/base/command/CommandDispatcher.spec.ts | 21 ---- src/base/command/CommandDispatcher.ts | 11 -- src/base/command/CommandRegistry.spec.ts | 10 +- src/base/command/validators.spec.ts | 30 ++++- src/base/command/validators.ts | 3 +- src/commands/test/TestCommand.ts | 18 +-- .../setGuildGameOffersEnabled.spec.ts | 25 ----- 9 files changed, 49 insertions(+), 210 deletions(-) delete mode 100644 src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts diff --git a/src/base/command/Command.spec.ts b/src/base/command/Command.spec.ts index 5923ac5..7dba9c5 100644 --- a/src/base/command/Command.spec.ts +++ b/src/base/command/Command.spec.ts @@ -1,5 +1,5 @@ import { Command } from './Command'; -import { ChatInputCommandInteraction, DMChannel, SlashCommandBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; import { ExtendedClient } from '../client/ExtendedClient'; describe('Base > Command > Command', () => { @@ -16,10 +16,6 @@ describe('Base > Command > Command', () => { public constructor(client: ExtendedClient) { super(client, { name: 'command', - description: 'description', - emoji: ':a:', - guildOnly: true, - permissions: ['ManageGuild', 'ManageChannels'] as const, builder: new SlashCommandBuilder() }); } @@ -38,45 +34,8 @@ describe('Base > Command > Command', () => { const command = new ConcreteCommand(client); expect(command.name).toBe('command'); - expect(command.description).toBe('description'); - expect(command.emoji).toBe(':a:'); - expect(command.guildOnly).toBe(true); - expect(command.permissions).toStrictEqual(['ManageGuild', 'ManageChannels']); expect(command.builder).toBeInstanceOf(SlashCommandBuilder); }); - - it('should set default options if not provided.', () => { - class ConcreteDefaultCommand extends Command { - public constructor(client: ExtendedClient) { - super(client, { - name: 'command', - description: 'description', - guildOnly: true, - builder: new SlashCommandBuilder() - }); - } - - public override run() { - throw new Error(); - } - } - - const command = new ConcreteDefaultCommand(client); - - expect(command.name).toBe('command'); - expect(command.description).toBe('description'); - expect(command.emoji).toBe(':robot:'); - expect(command.guildOnly).toBe(true); - expect(command.permissions).toBeNull(); - expect(command.builder).toBeInstanceOf(SlashCommandBuilder); - }); - - it('should set properties to builder.', () => { - const command = new ConcreteCommand(client); - - expect(command.builder.name).toBe('command'); - expect(command.builder.description).toBe('description'); - }); }); describe('onError()', () => { @@ -107,68 +66,5 @@ describe('Base > Command > Command', () => { expect(interaction.reply).toHaveBeenCalledWith({ content: 'An error has occurred when running the command command.' }); }); }); - - describe('hasPermission()', () => { - const missingMock = jest.fn().mockReturnValue(undefined); - const permissionsForMock = jest.fn().mockReturnValue({ missing: missingMock }); - - const command = new ConcreteCommand(client); - const interaction = { - user: { - id: '123' - }, - channel: { - permissionsFor: permissionsForMock - } - } as unknown as ChatInputCommandInteraction; - - it('should return true if command has no permissions set.', () => { - const command = new ConcreteCommand(client); - Object.defineProperty(command, 'permissions', { value: null }); - - const result = command.hasPermission(interaction); - expect(result).toBe(true); - }); - - it('should return true if interaction does not come from a channel.', () => { - const result = command.hasPermission({ ...interaction, channel: null } as ChatInputCommandInteraction); - expect(result).toBe(true); - }); - - it('should return true if interaction comes from a DM channel.', () => { - const channel = Object.create(DMChannel.prototype); - const result = command.hasPermission({ ...interaction, channel } as ChatInputCommandInteraction); - - expect(result).toBe(true); - }); - - it('should return true if interaction comes from a partial channel.', () => { - const channel = { partial: true }; - const result = command.hasPermission({ ...interaction, channel } as ChatInputCommandInteraction); - - expect(result).toBe(true); - }); - - it('should return true if no permissions are missing.', () => { - missingMock.mockReturnValueOnce([]); - const result = command.hasPermission(interaction); - - expect(result).toBe(true); - }); - - it('should return error message if 1 permission is missing.', () => { - missingMock.mockReturnValueOnce(['ManageGuild']); - const result = command.hasPermission(interaction); - - expect(result).toBe('The command command requires you to have the permissions: ManageGuild.'); - }); - - it('should return error message if multiple permissions are missing.', () => { - missingMock.mockReturnValueOnce(['ManageGuild', 'ManageChannels']); - const result = command.hasPermission(interaction); - - expect(result).toBe('The command command requires you to have the permissions: ManageGuild, ManageChannels.'); - }); - }); }); }); diff --git a/src/base/command/Command.ts b/src/base/command/Command.ts index 19a1ca1..7aa46b0 100644 --- a/src/base/command/Command.ts +++ b/src/base/command/Command.ts @@ -1,12 +1,8 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder, PermissionResolvable, DMChannel, PermissionsBitField, SlashCommandOptionsOnlyBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder } from 'discord.js'; import { ExtendedClient } from '../client/ExtendedClient'; export interface CommandOptions { name: string - description: string - emoji?: string - guildOnly: boolean - permissions?: PermissionResolvable | null builder: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder } @@ -14,23 +10,13 @@ export abstract class Command { public readonly client: ExtendedClient; public readonly name: string; - public readonly description: string; - public readonly emoji: string; - public readonly guildOnly: boolean; - public readonly permissions: PermissionResolvable | null; public readonly builder: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder; protected constructor(client: ExtendedClient, options: CommandOptions) { this.client = client; this.name = options.name; - this.description = options.description; - this.emoji = options.emoji || ':robot:'; - this.guildOnly = options.guildOnly; - this.permissions = options.permissions ?? null; - this.builder = options.builder - .setName(this.name) - .setDescription(this.description); + this.builder = options.builder; } public abstract run(interaction: ChatInputCommandInteraction): Promise<void> | void; @@ -47,21 +33,4 @@ export abstract class Command { await interaction.reply({ content: message }); } - - public hasPermission(interaction: ChatInputCommandInteraction): boolean | string { - if (!this.permissions || !interaction.channel) { - return true; - } - - if (interaction.channel instanceof DMChannel || interaction.channel.partial) { - return true; - } - - const missingPermissions = interaction.channel.permissionsFor(interaction.user.id)?.missing(PermissionsBitField.resolve(this.permissions)); - if (!missingPermissions?.length) { - return true; - } - - return `The command ${this.name} requires you to have the permissions: ${missingPermissions.join(', ')}.`; - } } diff --git a/src/base/command/CommandDispatcher.spec.ts b/src/base/command/CommandDispatcher.spec.ts index 21d61c8..b392b95 100644 --- a/src/base/command/CommandDispatcher.spec.ts +++ b/src/base/command/CommandDispatcher.spec.ts @@ -34,9 +34,7 @@ describe('Base > Command > CommandDispatcher', () => { const command = { name: 'command', - guildOnly: false, run: jest.fn(), - hasPermission: jest.fn().mockReturnValue(true), onError: jest.fn() } as unknown as Command; @@ -64,25 +62,6 @@ describe('Base > Command > CommandDispatcher', () => { expect(command.run).not.toHaveBeenCalled(); }); - it('should reply interaction and not execute command if command is guildOnly and interaction was not sent in guild.', async () => { - (client.registry.get as jest.Mock).mockReturnValueOnce({ ...command, guildOnly: true }); - (interaction.inGuild as unknown as jest.Mock).mockReturnValueOnce(false); - await dispatcher.handleInteraction(interaction); - - expect(interaction.reply).toHaveBeenCalledWith({ content: 'I can only run this from a server.' }); - expect(client.emit).not.toHaveBeenCalled(); - expect(command.run).not.toHaveBeenCalled(); - }); - - it('should reply interaction and not execute command if author has no permission to execute.', async () => { - (command.hasPermission as jest.Mock).mockReturnValueOnce('No permissions.'); - await dispatcher.handleInteraction(interaction); - - expect(interaction.reply).toHaveBeenCalledWith({ content: 'No permissions.' }); - expect(client.emit).not.toHaveBeenCalled(); - expect(command.run).not.toHaveBeenCalled(); - }); - it('should execute command if all is well.', async () => { await dispatcher.handleInteraction(interaction); diff --git a/src/base/command/CommandDispatcher.ts b/src/base/command/CommandDispatcher.ts index 2063991..8823458 100644 --- a/src/base/command/CommandDispatcher.ts +++ b/src/base/command/CommandDispatcher.ts @@ -24,18 +24,7 @@ export class CommandDispatcher { return; } - if (command.guildOnly && !interaction.inGuild()) { - await interaction.reply({ content: 'I can only run this from a server.' }); - return; - } - try { - const hasPermission = command.hasPermission(interaction); - if (typeof hasPermission === 'string') { - await interaction.reply({ content: hasPermission }); - return; - } - this.client.emit('commandExecute', command, interaction); await command.run(interaction); } catch (error) { diff --git a/src/base/command/CommandRegistry.spec.ts b/src/base/command/CommandRegistry.spec.ts index c84c82a..7cc5735 100644 --- a/src/base/command/CommandRegistry.spec.ts +++ b/src/base/command/CommandRegistry.spec.ts @@ -20,11 +20,17 @@ describe('Base > Command > CommandRegistry', () => { const command = { name: 'command', - description: 'A little description.' + builder: { + name: 'command', + description: 'A little description.' + } } as Command; const command2 = { name: 'cmd', - description: 'A little description.' + builder: { + name: 'cmd', + description: 'A little description.' + } } as Command; beforeEach(() => { diff --git a/src/base/command/validators.spec.ts b/src/base/command/validators.spec.ts index 520af15..45648f7 100644 --- a/src/base/command/validators.spec.ts +++ b/src/base/command/validators.spec.ts @@ -6,7 +6,10 @@ describe('Base > Command > Validators', () => { describe('validateCommand()', () => { const command = { name: 'command', - description: 'A little description.' + builder: { + name: 'command', + description: 'A little description.' + } } as Command; it('should be defined.', () => { @@ -30,15 +33,32 @@ describe('Base > Command > Validators', () => { expect(() => validateCommand({ ...command, name } as Command)).toThrow(expectedError); }); - it('should throw CommandValidationError if description is too short.', () => { + it('should throw CommandValidationError if builder name is too short.', () => { + const expectedError = new CommandValidationError('Command / has an invalid name. It must be between 1 and 32 characters.'); + expect(() => validateCommand({ ...command, builder: { ...command.builder, name: '' } } as Command)).toThrow(expectedError); + }); + + it('should throw CommandValidationError if builder name is too long.', () => { + const name = 'a'.repeat(35); + const expectedError = new CommandValidationError(`Command /${name} has an invalid name. It must be between 1 and 32 characters.`); + expect(() => validateCommand({ ...command, builder: { ...command.builder, name } } as Command)).toThrow(expectedError); + }); + + it('should throw CommandValidationError if builder name contains upper case letters.', () => { + const name = 'COmmand'; + const expectedError = new CommandValidationError(`Command /${name} has an invalid name. Please use all lower-cased characters.`); + expect(() => validateCommand({ ...command, builder: { ...command.builder, name } } as Command)).toThrow(expectedError); + }); + + it('should throw CommandValidationError if builder description is too short.', () => { const expectedError = new CommandValidationError('Command /command has an invalid description. It must be between 1 and 100 characters.'); - expect(() => validateCommand({ ...command, description: '' } as Command)).toThrow(expectedError); + expect(() => validateCommand({ ...command, builder: { ...command.builder, description: '' } } as Command)).toThrow(expectedError); }); - it('should throw CommandValidationError if description is too long.', () => { + it('should throw CommandValidationError if bulder description is too long.', () => { const description = 'a'.repeat(101); const expectedError = new CommandValidationError('Command /command has an invalid description. It must be between 1 and 100 characters.'); - expect(() => validateCommand({ ...command, description } as Command)).toThrow(expectedError); + expect(() => validateCommand({ ...command, builder: { ...command.builder, description } } as Command)).toThrow(expectedError); }); }); }); diff --git a/src/base/command/validators.ts b/src/base/command/validators.ts index 6aeb905..1086ee9 100644 --- a/src/base/command/validators.ts +++ b/src/base/command/validators.ts @@ -26,5 +26,6 @@ const validateDescription = (name: string, description: string) => { export const validateCommand = (command: Command) => { validateName(command.name); - validateDescription(command.name, command.description); + validateName(command.builder.name); + validateDescription(command.name, command.builder.description); }; diff --git a/src/commands/test/TestCommand.ts b/src/commands/test/TestCommand.ts index 9a62a65..b25a303 100644 --- a/src/commands/test/TestCommand.ts +++ b/src/commands/test/TestCommand.ts @@ -1,21 +1,25 @@ -import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; import { Command } from '../../base/command/Command'; import { ExtendedClient } from '../../base/client/ExtendedClient'; -import { getCurrentGameOffers } from '../../features/gameOffers/functions/getCurrentGameOffers'; +import { deleteGuild } from '../../features/gameOffers/functions/deleteGuild'; export default class TestCommand extends Command { public constructor(client: ExtendedClient) { super(client, { name: 'test', - description: 'Test', - guildOnly: true, - permissions: [PermissionFlagsBits.ManageChannels] as const, builder: new SlashCommandBuilder() + .addChannelOption((input) => { + return input + .setName('channel') + .setDescription('The channel to use.') + .setRequired(true); + }) }); } - public override async run(interaction: ChatInputCommandInteraction): Promise<void> { - const result = await getCurrentGameOffers(); + public override async run(interaction: ChatInputCommandInteraction<'raw' | 'cached'>): Promise<void> { + const result = await deleteGuild(interaction.guildId); + await interaction.reply({ content: ` \`\`\` ${JSON.stringify(result, null, 2)} diff --git a/src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts b/src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts deleted file mode 100644 index 09a51c7..0000000 --- a/src/features/gameOffers/functions/setGuildGameOffersEnabled.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { setGuildGameOfferEnabled } from './setGuildGameOfferEnabled'; -import { db } from '../../../services/database/client'; - -jest.mock('../../../services/database/client'); - -describe('Features > GameOffers > Functions > SetGuildGameOffersEnabled', () => { - const guildId = '1267881983548063785'; - - beforeAll(() => { - (db.any as jest.Mock).mockImplementation(() => Promise.resolve()); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should be defined.', () => { - expect(setGuildGameOfferEnabled).toBeDefined(); - }); - - it('should return undefined.', async () => { - const result = await setGuildGameOfferEnabled(guildId, 'EpicGames', true); - expect(result).toBeUndefined(); - }); -}); From 6b8c6737df58499c09839bf4161596a30e3201e7 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 12:07:21 -0500 Subject: [PATCH 20/52] Renamed CommandDispatcher to InteractionDIspatcher. --- src/base/client/ExtendedClient.spec.ts | 4 ++-- src/base/client/ExtendedClient.ts | 6 +++--- ...ispatcher.spec.ts => InteractionDispatcher.spec.ts} | 10 +++++----- .../{CommandDispatcher.ts => InteractionDispatcher.ts} | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename src/base/command/{CommandDispatcher.spec.ts => InteractionDispatcher.spec.ts} (91%) rename src/base/command/{CommandDispatcher.ts => InteractionDispatcher.ts} (96%) diff --git a/src/base/client/ExtendedClient.spec.ts b/src/base/client/ExtendedClient.spec.ts index a6f560b..6a722da 100644 --- a/src/base/client/ExtendedClient.spec.ts +++ b/src/base/client/ExtendedClient.spec.ts @@ -1,9 +1,9 @@ import { ExtendedClient } from './ExtendedClient'; import { GatewayIntentBits, Interaction } from 'discord.js'; -jest.mock('../command/CommandDispatcher', () => { +jest.mock('../command/InteractionDispatcher', () => { return { - CommandDispatcher: jest.fn().mockImplementation(() => { + InteractionDispatcher: jest.fn().mockImplementation(() => { return { handleInteraction: jest.fn() }; diff --git a/src/base/client/ExtendedClient.ts b/src/base/client/ExtendedClient.ts index 082dad1..809e3a7 100644 --- a/src/base/client/ExtendedClient.ts +++ b/src/base/client/ExtendedClient.ts @@ -1,6 +1,6 @@ import { Client, ClientOptions, ClientEvents, ChatInputCommandInteraction } from 'discord.js'; import { CommandRegistry } from '../command/CommandRegistry'; -import { CommandDispatcher } from '../command/CommandDispatcher'; +import { InteractionDispatcher } from '../command/InteractionDispatcher'; import { Command } from '../command/Command'; export interface ExtendedClientEvents extends ClientEvents { @@ -37,13 +37,13 @@ export declare interface ExtendedClient { export class ExtendedClient extends Client { public readonly registry: CommandRegistry; - public readonly dispatcher: CommandDispatcher; + public readonly dispatcher: InteractionDispatcher; public constructor(options: ClientOptions) { super(options); this.registry = new CommandRegistry(this); - this.dispatcher = new CommandDispatcher(this, this.registry); + this.dispatcher = new InteractionDispatcher(this, this.registry); this.registerBasicHandlers(); } diff --git a/src/base/command/CommandDispatcher.spec.ts b/src/base/command/InteractionDispatcher.spec.ts similarity index 91% rename from src/base/command/CommandDispatcher.spec.ts rename to src/base/command/InteractionDispatcher.spec.ts index b392b95..27557f3 100644 --- a/src/base/command/CommandDispatcher.spec.ts +++ b/src/base/command/InteractionDispatcher.spec.ts @@ -1,4 +1,4 @@ -import { CommandDispatcher } from './CommandDispatcher'; +import { InteractionDispatcher } from './InteractionDispatcher'; import { ChatInputCommandInteraction } from 'discord.js'; import logger from '@moonstar-x/logger'; import { ExtendedClient } from '../client/ExtendedClient'; @@ -6,12 +6,12 @@ import { Command } from './Command'; jest.mock('@moonstar-x/logger'); -describe('Base > Command > CommandDispatcher', () => { +describe('Base > Command > InteractionDispatcher', () => { beforeEach(() => { jest.clearAllMocks(); }); - describe('class CommandDispatcher', () => { + describe('class InteractionDispatcher', () => { const client = { emit: jest.fn(), registry: { @@ -20,11 +20,11 @@ describe('Base > Command > CommandDispatcher', () => { } as unknown as ExtendedClient; it('should be defined.', () => { - expect(CommandDispatcher).toBeDefined(); + expect(InteractionDispatcher).toBeDefined(); }); describe('handleInteraction()', () => { - const dispatcher = new CommandDispatcher(client, client.registry); + const dispatcher = new InteractionDispatcher(client, client.registry); const interaction = { isChatInputCommand: jest.fn().mockReturnValue(true), diff --git a/src/base/command/CommandDispatcher.ts b/src/base/command/InteractionDispatcher.ts similarity index 96% rename from src/base/command/CommandDispatcher.ts rename to src/base/command/InteractionDispatcher.ts index 8823458..4917d16 100644 --- a/src/base/command/CommandDispatcher.ts +++ b/src/base/command/InteractionDispatcher.ts @@ -5,7 +5,7 @@ import { CommandRegistry } from './CommandRegistry'; import { isChatInputCommand } from '../types/guards'; -export class CommandDispatcher { +export class InteractionDispatcher { public readonly client: ExtendedClient; public readonly registry: CommandRegistry; From 6a5792f693d94639805595389107409d021ad8e8 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 12:15:18 -0500 Subject: [PATCH 21/52] Replaced InteractionDispatcher logic to allow for more types of interactions. --- .../command/InteractionDispatcher.spec.ts | 136 +++++++++--------- src/base/command/InteractionDispatcher.ts | 12 +- src/base/types/guards.spec.ts | 18 +++ 3 files changed, 95 insertions(+), 71 deletions(-) create mode 100644 src/base/types/guards.spec.ts diff --git a/src/base/command/InteractionDispatcher.spec.ts b/src/base/command/InteractionDispatcher.spec.ts index 27557f3..02f10f1 100644 --- a/src/base/command/InteractionDispatcher.spec.ts +++ b/src/base/command/InteractionDispatcher.spec.ts @@ -24,73 +24,75 @@ describe('Base > Command > InteractionDispatcher', () => { }); describe('handleInteraction()', () => { - const dispatcher = new InteractionDispatcher(client, client.registry); - - const interaction = { - isChatInputCommand: jest.fn().mockReturnValue(true), - inGuild: jest.fn().mockReturnValue(true), - reply: jest.fn() - } as unknown as ChatInputCommandInteraction; - - const command = { - name: 'command', - run: jest.fn(), - onError: jest.fn() - } as unknown as Command; - - beforeAll(() => { - (client.registry.get as jest.Mock).mockReturnValue(command); - }); - - afterAll(() => { - (client.registry.get as jest.Mock).mockReset(); - }); - - it('should not execute command if interaction is not a chat input command.', async () => { - (interaction.isChatInputCommand as unknown as jest.Mock).mockReturnValueOnce(false); - await dispatcher.handleInteraction(interaction); - - expect(client.emit).not.toHaveBeenCalled(); - expect(command.run).not.toHaveBeenCalled(); - }); - - it('should not execute command if registry cannot find it.', async () => { - (client.registry.get as jest.Mock).mockReturnValueOnce(null); - await dispatcher.handleInteraction(interaction); - - expect(client.emit).not.toHaveBeenCalled(); - expect(command.run).not.toHaveBeenCalled(); - }); - - it('should execute command if all is well.', async () => { - await dispatcher.handleInteraction(interaction); - - expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); - expect(command.run).toHaveBeenCalledWith(interaction); - }); - - it('should handle error with command.onError if run fails.', async () => { - const error = new Error('Oops!'); - (command.run as jest.Mock).mockRejectedValueOnce(error); - await dispatcher.handleInteraction(interaction); - - expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); - expect(command.run).toHaveBeenCalledWith(interaction); - expect(command.onError).toHaveBeenCalledWith(error, interaction); - }); - - it('should log error if command.onError fails.', async () => { - const originalError = new Error('Oops!'); - const innerError = new Error('Ouch!'); - (command.run as jest.Mock).mockRejectedValueOnce(originalError); - (command.onError as jest.Mock).mockRejectedValueOnce(innerError); - await dispatcher.handleInteraction(interaction); - - expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); - expect(command.run).toHaveBeenCalledWith(interaction); - expect(command.onError).toHaveBeenCalledWith(originalError, interaction); - expect(logger.error).toHaveBeenCalledWith('There was an error in the command execution for command.', originalError); - expect(logger.error).toHaveBeenCalledWith('Additionally, the error handling could not complete.', innerError); + describe('For ChatInputCommandInteraction', () => { + const dispatcher = new InteractionDispatcher(client, client.registry); + + const interaction = { + isChatInputCommand: jest.fn().mockReturnValue(true), + inGuild: jest.fn().mockReturnValue(true), + reply: jest.fn() + } as unknown as ChatInputCommandInteraction; + + const command = { + name: 'command', + run: jest.fn(), + onError: jest.fn() + } as unknown as Command; + + beforeAll(() => { + (client.registry.get as jest.Mock).mockReturnValue(command); + }); + + afterAll(() => { + (client.registry.get as jest.Mock).mockReset(); + }); + + it('should not execute command if interaction is not a chat input command.', async () => { + (interaction.isChatInputCommand as unknown as jest.Mock).mockReturnValueOnce(false); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).not.toHaveBeenCalled(); + expect(command.run).not.toHaveBeenCalled(); + }); + + it('should not execute command if registry cannot find it.', async () => { + (client.registry.get as jest.Mock).mockReturnValueOnce(null); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).not.toHaveBeenCalled(); + expect(command.run).not.toHaveBeenCalled(); + }); + + it('should execute command if all is well.', async () => { + await dispatcher.handleInteraction(interaction); + + expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); + expect(command.run).toHaveBeenCalledWith(interaction); + }); + + it('should handle error with command.onError if run fails.', async () => { + const error = new Error('Oops!'); + (command.run as jest.Mock).mockRejectedValueOnce(error); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); + expect(command.run).toHaveBeenCalledWith(interaction); + expect(command.onError).toHaveBeenCalledWith(error, interaction); + }); + + it('should log error if command.onError fails.', async () => { + const originalError = new Error('Oops!'); + const innerError = new Error('Ouch!'); + (command.run as jest.Mock).mockRejectedValueOnce(originalError); + (command.onError as jest.Mock).mockRejectedValueOnce(innerError); + await dispatcher.handleInteraction(interaction); + + expect(client.emit).toHaveBeenCalledWith('commandExecute', command, interaction); + expect(command.run).toHaveBeenCalledWith(interaction); + expect(command.onError).toHaveBeenCalledWith(originalError, interaction); + expect(logger.error).toHaveBeenCalledWith('There was an error in the command execution for command.', originalError); + expect(logger.error).toHaveBeenCalledWith('Additionally, the error handling could not complete.', innerError); + }); }); }); }); diff --git a/src/base/command/InteractionDispatcher.ts b/src/base/command/InteractionDispatcher.ts index 4917d16..c779a56 100644 --- a/src/base/command/InteractionDispatcher.ts +++ b/src/base/command/InteractionDispatcher.ts @@ -1,4 +1,4 @@ -import { BaseInteraction } from 'discord.js'; +import { BaseInteraction, ChatInputCommandInteraction } from 'discord.js'; import logger from '@moonstar-x/logger'; import { ExtendedClient } from '../client/ExtendedClient'; import { CommandRegistry } from './CommandRegistry'; @@ -14,11 +14,15 @@ export class InteractionDispatcher { this.registry = registry; } - public async handleInteraction(interaction: BaseInteraction): Promise<void> { - if (!isChatInputCommand(interaction)) { - return; + public handleInteraction(interaction: BaseInteraction): Promise<void> { + if (isChatInputCommand(interaction)) { + return this.handleChatInputCommand(interaction); } + return Promise.resolve(); + } + + private async handleChatInputCommand(interaction: ChatInputCommandInteraction): Promise<void> { const command = this.registry.get(interaction.commandName); if (!command) { return; diff --git a/src/base/types/guards.spec.ts b/src/base/types/guards.spec.ts new file mode 100644 index 0000000..7954624 --- /dev/null +++ b/src/base/types/guards.spec.ts @@ -0,0 +1,18 @@ +import { isChatInputCommand } from './guards'; +import { BaseInteraction } from 'discord.js'; + +describe('Base > Types > Guards', () => { + describe('isChatInputCommand()', () => { + const interaction = { + isChatInputCommand: jest.fn().mockReturnValue(true) + } as unknown as BaseInteraction; + + it('should be defined.', () => { + expect(isChatInputCommand).toBeDefined(); + }); + + it('should return interaction.isChatInputCommand.', () => { + expect(isChatInputCommand(interaction)).toBe(true); + }); + }); +}); From 982821b46290b2c18e11f463618649eda6a789f9 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 12:31:00 -0500 Subject: [PATCH 22/52] Guild leave event now deletes guild data. --- src/base/client/ClientEventHandlers.spec.ts | 33 +++++++++++++++++++-- src/base/client/ClientEventHandlers.ts | 12 ++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/base/client/ClientEventHandlers.spec.ts b/src/base/client/ClientEventHandlers.spec.ts index eff9630..cde8dfb 100644 --- a/src/base/client/ClientEventHandlers.spec.ts +++ b/src/base/client/ClientEventHandlers.spec.ts @@ -2,9 +2,16 @@ import * as ClientEventHandlers from './ClientEventHandlers'; import logger from '@moonstar-x/logger'; import { ChatInputCommandInteraction, Guild, GuildMember, User } from 'discord.js'; import { Command } from '../command/Command'; +import { deleteGuild } from '../../features/gameOffers/functions/deleteGuild'; jest.mock('@moonstar-x/logger'); +jest.mock('../../features/gameOffers/functions/deleteGuild', () => { + return { + deleteGuild: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + describe('Base > Client > ClientEventHandlers', () => { beforeEach(() => { jest.clearAllMocks(); @@ -50,16 +57,36 @@ describe('Base > Client > ClientEventHandlers', () => { }); describe('ClientEventHandlers.handleGuildDelete()', () => { - const guild = { name: 'Guild' } as Guild; + const guild = { name: 'Guild', id: '123' } as Guild; it('should be defined.', () => { expect(ClientEventHandlers.handleGuildDelete).toBeDefined(); }); - it('should log guild leave.', () => { - ClientEventHandlers.handleGuildDelete(guild); + it('should log guild leave.', async () => { + await ClientEventHandlers.handleGuildDelete(guild); expect(logger.info).toHaveBeenCalledWith('Left guild Guild.'); }); + + it('should delete guild data.', async () => { + await ClientEventHandlers.handleGuildDelete(guild); + expect(deleteGuild).toHaveBeenCalledWith('123'); + }); + + it('should log guild data delete.', async () => { + await ClientEventHandlers.handleGuildDelete(guild); + expect(logger.info).toHaveBeenCalledWith('Deleted guild data for Guild.'); + }); + + + it('should error log if delete guild data fails.', async () => { + const error = new Error('Oops!'); + (deleteGuild as jest.Mock).mockRejectedValueOnce(error); + + await ClientEventHandlers.handleGuildDelete(guild); + expect(logger.error).toHaveBeenCalledWith('Could not delete guild data for Guild.'); + expect(logger.error).toHaveBeenCalledWith(error); + }); }); describe('ClientEventHandlers.handleGuildUnavailable()', () => { diff --git a/src/base/client/ClientEventHandlers.ts b/src/base/client/ClientEventHandlers.ts index 7942a60..20861f9 100644 --- a/src/base/client/ClientEventHandlers.ts +++ b/src/base/client/ClientEventHandlers.ts @@ -1,6 +1,7 @@ import { ChatInputCommandInteraction, Guild, GuildMember } from 'discord.js'; import logger from '@moonstar-x/logger'; import { Command } from '../command/Command'; +import { deleteGuild } from '../../features/gameOffers/functions/deleteGuild'; export const handleDebug = (info: string) => { logger.debug(info); @@ -14,9 +15,16 @@ export const handleGuildCreate = (guild: Guild) => { logger.info(`Joined guild ${guild.name}.`); }; -// TODO: Delete guild data in database. -export const handleGuildDelete = (guild: Guild) => { +export const handleGuildDelete = async (guild: Guild) => { logger.info(`Left guild ${guild.name}.`); + + try { + await deleteGuild(guild.id); + logger.info(`Deleted guild data for ${guild.name}.`); + } catch (error) { + logger.error(`Could not delete guild data for ${guild.name}.`); + logger.error(error); + } }; export const handleGuildUnavailable = (guild: Guild) => { From 46ffaed92aa472df3b9d868be5df38e309c4328b Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 13:25:04 -0500 Subject: [PATCH 23/52] Created basic commands to be implemented. --- src/commands/ConfigureCommand.spec.ts | 7 ++++++ src/commands/ConfigureCommand.ts | 34 +++++++++++++++++++++++++++ src/commands/HelpCommand.spec.ts | 7 ++++++ src/commands/HelpCommand.ts | 32 +++++++++++++++++++++++++ src/commands/InfoCommand.spec.ts | 7 ++++++ src/commands/InfoCommand.ts | 33 ++++++++++++++++++++++++++ src/commands/OffersCommand.spec.ts | 7 ++++++ src/commands/OffersCommand.ts | 32 +++++++++++++++++++++++++ src/commands/test/TestCommand.ts | 29 ----------------------- 9 files changed, 159 insertions(+), 29 deletions(-) create mode 100644 src/commands/ConfigureCommand.spec.ts create mode 100644 src/commands/ConfigureCommand.ts create mode 100644 src/commands/HelpCommand.spec.ts create mode 100644 src/commands/HelpCommand.ts create mode 100644 src/commands/InfoCommand.spec.ts create mode 100644 src/commands/InfoCommand.ts create mode 100644 src/commands/OffersCommand.spec.ts create mode 100644 src/commands/OffersCommand.ts delete mode 100644 src/commands/test/TestCommand.ts diff --git a/src/commands/ConfigureCommand.spec.ts b/src/commands/ConfigureCommand.spec.ts new file mode 100644 index 0000000..745ec35 --- /dev/null +++ b/src/commands/ConfigureCommand.spec.ts @@ -0,0 +1,7 @@ +import ConfigureCommand from './ConfigureCommand'; + +describe('Commands > ConfigureCommand', () => { + it('should be defined.', () => { + expect(ConfigureCommand).toBeDefined(); + }); +}); diff --git a/src/commands/ConfigureCommand.ts b/src/commands/ConfigureCommand.ts new file mode 100644 index 0000000..0d1cfca --- /dev/null +++ b/src/commands/ConfigureCommand.ts @@ -0,0 +1,34 @@ +import { Command } from '../base/command/Command'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; + +export default class ConfigureCommand extends Command { + public constructor(client: ExtendedClient) { + super(client, { + name: 'configure', + builder: new SlashCommandBuilder() + .setName('configure') + .setNameLocalizations({ + 'en-US': 'configure', + 'en-GB': 'configure', + 'es-ES': 'configurar', + 'es-419': 'configurar', + fr: 'configurer' + }) + .setDescription('Change the configuration for this server.') + .setDescriptionLocalizations({ + 'en-US': 'Change the configuration for this server.', + 'en-GB': 'Change the configuration for this server.', + 'es-ES': 'Cambia la configuración para este servidor.', + 'es-419': 'Cambia la configuración para este servidor.', + fr: 'Changez la configuration pour ce serveur.' + }) + .setDMPermission(false) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) + }); + } + + public override async run(interaction: ChatInputCommandInteraction): Promise<void> { + await interaction.reply({ content: 'Hi' }); + } +} diff --git a/src/commands/HelpCommand.spec.ts b/src/commands/HelpCommand.spec.ts new file mode 100644 index 0000000..445d0b6 --- /dev/null +++ b/src/commands/HelpCommand.spec.ts @@ -0,0 +1,7 @@ +import HelpCommand from './HelpCommand'; + +describe('Commands > HelpCommand', () => { + it('should be defined.', () => { + expect(HelpCommand).toBeDefined(); + }); +}); diff --git a/src/commands/HelpCommand.ts b/src/commands/HelpCommand.ts new file mode 100644 index 0000000..2f2777b --- /dev/null +++ b/src/commands/HelpCommand.ts @@ -0,0 +1,32 @@ +import { Command } from '../base/command/Command'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; + +export default class HelpCommand extends Command { + public constructor(client: ExtendedClient) { + super(client, { + name: 'help', + builder: new SlashCommandBuilder() + .setName('help') + .setNameLocalizations({ + 'en-US': 'help', + 'en-GB': 'help', + 'es-ES': 'ayuda', + 'es-419': 'ayuda', + fr: 'aide' + }) + .setDescription('Help and usage information.') + .setDescriptionLocalizations({ + 'en-US': 'Help and usage information.', + 'en-GB': 'Help and usage information.', + 'es-ES': 'Información de ayuda y utilización.', + 'es-419': 'Información de ayuda y utilización.', + fr: "Information d'aide et d'utilisation." + }) + }); + } + + public override async run(interaction: ChatInputCommandInteraction): Promise<void> { + await interaction.reply({ content: 'Hi' }); + } +} diff --git a/src/commands/InfoCommand.spec.ts b/src/commands/InfoCommand.spec.ts new file mode 100644 index 0000000..0e0e995 --- /dev/null +++ b/src/commands/InfoCommand.spec.ts @@ -0,0 +1,7 @@ +import InfoCommand from './InfoCommand'; + +describe('Commands > InfoCommand', () => { + it('should be defined.', () => { + expect(InfoCommand).toBeDefined(); + }); +}); diff --git a/src/commands/InfoCommand.ts b/src/commands/InfoCommand.ts new file mode 100644 index 0000000..c13f5dc --- /dev/null +++ b/src/commands/InfoCommand.ts @@ -0,0 +1,33 @@ +import { Command } from '../base/command/Command'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; + +export default class InfoCommand extends Command { + public constructor(client: ExtendedClient) { + super(client, { + name: 'info', + builder: new SlashCommandBuilder() + .setName('info') + .setNameLocalizations({ + 'en-US': 'info', + 'en-GB': 'info', + 'es-ES': 'info', + 'es-419': 'info', + fr: 'info' + }) + .setDescription('Get the settings information saved for this server.') + .setDescriptionLocalizations({ + 'en-US': 'Get the settings information saved for this server.', + 'en-GB': 'Get the settings information saved for this server.', + 'es-ES': 'Obten la información de configuración para este servidor.', + 'es-419': 'Obten la información de configuración para este servidor.', + fr: 'Obtenez la information de configuration pour ce serveur.' + }) + .setDMPermission(false) + }); + } + + public override async run(interaction: ChatInputCommandInteraction): Promise<void> { + await interaction.reply({ content: 'Hi' }); + } +} diff --git a/src/commands/OffersCommand.spec.ts b/src/commands/OffersCommand.spec.ts new file mode 100644 index 0000000..6422001 --- /dev/null +++ b/src/commands/OffersCommand.spec.ts @@ -0,0 +1,7 @@ +import OffersCommand from './OffersCommand'; + +describe('Commands > OffersCommand', () => { + it('should be defined.', () => { + expect(OffersCommand).toBeDefined(); + }); +}); diff --git a/src/commands/OffersCommand.ts b/src/commands/OffersCommand.ts new file mode 100644 index 0000000..87ea1ba --- /dev/null +++ b/src/commands/OffersCommand.ts @@ -0,0 +1,32 @@ +import { Command } from '../base/command/Command'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; + +export default class OffersCommand extends Command { + public constructor(client: ExtendedClient) { + super(client, { + name: 'offers', + builder: new SlashCommandBuilder() + .setName('offers') + .setNameLocalizations({ + 'en-US': 'offers', + 'en-GB': 'offers', + 'es-ES': 'ofertas', + 'es-419': 'ofertas', + fr: 'offres' + }) + .setDescription('Get a list of free games currently offered.') + .setDescriptionLocalizations({ + 'en-US': 'Get a list of free games currently offered.', + 'en-GB': 'Get a list of free games currently offered.', + 'es-ES': 'Obten una lista de los juegos gratis en oferta.', + 'es-419': 'Obten una lista de los juegos gratis en oferta.', + fr: 'Obtenez une liste des jeux offerts actuellement.' + }) + }); + } + + public override async run(interaction: ChatInputCommandInteraction): Promise<void> { + await interaction.reply({ content: 'Hi' }); + } +} diff --git a/src/commands/test/TestCommand.ts b/src/commands/test/TestCommand.ts deleted file mode 100644 index b25a303..0000000 --- a/src/commands/test/TestCommand.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { Command } from '../../base/command/Command'; -import { ExtendedClient } from '../../base/client/ExtendedClient'; -import { deleteGuild } from '../../features/gameOffers/functions/deleteGuild'; - -export default class TestCommand extends Command { - public constructor(client: ExtendedClient) { - super(client, { - name: 'test', - builder: new SlashCommandBuilder() - .addChannelOption((input) => { - return input - .setName('channel') - .setDescription('The channel to use.') - .setRequired(true); - }) - }); - } - - public override async run(interaction: ChatInputCommandInteraction<'raw' | 'cached'>): Promise<void> { - const result = await deleteGuild(interaction.guildId); - - await interaction.reply({ content: ` - \`\`\` -${JSON.stringify(result, null, 2)} - \`\`\` - ` }); - } -} From ab86f6ff18fec8441c9c8b1133e2b04dafe6e9ef Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 5 Aug 2024 18:44:58 -0500 Subject: [PATCH 24/52] Implemented a base for Configure and Info commands. --- src/base/command/Command.ts | 8 +-- src/base/types/aliases.ts | 3 ++ src/commands/ConfigureCommand.ts | 86 ++++++++++++++++++++++++++++++-- src/commands/InfoCommand.ts | 35 +++++++++++-- 4 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 src/base/types/aliases.ts diff --git a/src/base/command/Command.ts b/src/base/command/Command.ts index 7aa46b0..8b8af13 100644 --- a/src/base/command/Command.ts +++ b/src/base/command/Command.ts @@ -1,16 +1,18 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandsOnlyBuilder } from 'discord.js'; import { ExtendedClient } from '../client/ExtendedClient'; +export type AnySlashCommandBuilder = SlashCommandBuilder | SlashCommandOptionsOnlyBuilder | SlashCommandSubcommandsOnlyBuilder; + export interface CommandOptions { name: string - builder: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder + builder: AnySlashCommandBuilder } export abstract class Command { public readonly client: ExtendedClient; public readonly name: string; - public readonly builder: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder; + public readonly builder: AnySlashCommandBuilder; protected constructor(client: ExtendedClient, options: CommandOptions) { this.client = client; diff --git a/src/base/types/aliases.ts b/src/base/types/aliases.ts new file mode 100644 index 0000000..b75a465 --- /dev/null +++ b/src/base/types/aliases.ts @@ -0,0 +1,3 @@ +import { ChatInputCommandInteraction } from 'discord.js'; + +export type GuildChatInputCommandInteraction = ChatInputCommandInteraction<'raw' | 'cached'>; diff --git a/src/commands/ConfigureCommand.ts b/src/commands/ConfigureCommand.ts index 0d1cfca..fd60dfa 100644 --- a/src/commands/ConfigureCommand.ts +++ b/src/commands/ConfigureCommand.ts @@ -1,6 +1,10 @@ import { Command } from '../base/command/Command'; import { ExtendedClient } from '../base/client/ExtendedClient'; -import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; +import { updateOrCreateGuildChannel } from '../features/gameOffers/functions/updateOrCreateGuildChannel'; +import { GuildChatInputCommandInteraction } from '../base/types/aliases'; +import { getStorefronts } from '../features/gameOffers/functions/getStorefronts'; +import { setGuildGameOfferEnabled } from '../features/gameOffers/functions/setGuildGameOfferEnabled'; export default class ConfigureCommand extends Command { public constructor(client: ExtendedClient) { @@ -25,10 +29,86 @@ export default class ConfigureCommand extends Command { }) .setDMPermission(false) .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) + .addSubcommand((input) => { + return input + .setName('channel') + .setDescription('Set the notifications channel.') + .addChannelOption((input) => { + return input + .setName('channel') + .setDescription('The text channel to use.') + .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement) + .setRequired(true); + }); + }) + .addSubcommand((input) => { + return input + .setName('storefronts') + .setDescription('Enable or disable notifications from specific Storefronts.'); + }) }); } - public override async run(interaction: ChatInputCommandInteraction): Promise<void> { - await interaction.reply({ content: 'Hi' }); + public override async run(interaction: GuildChatInputCommandInteraction): Promise<void> { + const subCommand = interaction.options.getSubcommand(); + + switch (subCommand) { + case 'channel': + return this.runChannel(interaction); + case 'storefronts': + return this.runStorefronts(interaction); + default: + return this.runDefault(interaction); + } + } + + private async runChannel(interaction: GuildChatInputCommandInteraction): Promise<void> { + const channel = interaction.options.getChannel('channel'); + + if (!channel) { + await interaction.reply({ content: 'No channel received.' }); + return; + } + + await updateOrCreateGuildChannel(interaction.guildId, channel.id); + await interaction.reply({ content: `Updated notifications channel to ${channel}.` }); + } + + private async runStorefronts(interaction: GuildChatInputCommandInteraction): Promise<void> { + const storefronts = await getStorefronts(); + + if (!storefronts.length) { + await interaction.reply({ content: 'No storefronts are available.' }); + return; + } + + await interaction.reply({ content: 'Click on the enable or disable buttons for each Storefront to enable or disable it.' }); + + for (const storefront of storefronts) { + const enableButton = new ButtonBuilder() + .setCustomId('storefronts-enable') + .setLabel('Enable') + .setStyle(ButtonStyle.Primary); + + const disableButton = new ButtonBuilder() + .setCustomId('storefronts-disable') + .setLabel('Disable') + .setStyle(ButtonStyle.Secondary); + + const actionRow = new ActionRowBuilder() + .addComponents(enableButton, disableButton); + + const response = await interaction.followUp({ content: `Enable or disable **${storefront}**?`, components: [actionRow] as any }); + const confirmation = await response.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id && i.customId.startsWith('storefronts'), time: 60_000 }); + + const enabled = confirmation.customId === 'storefronts-enable'; + await setGuildGameOfferEnabled(interaction.guildId, storefront, enabled); + + await confirmation.update({ content: `Updated ${storefront} subscription.`, components: [] }); + } + } + + private async runDefault(interaction: ChatInputCommandInteraction): Promise<void> { + await interaction.reply({ content: 'Unknown subcommand received.' }); } } diff --git a/src/commands/InfoCommand.ts b/src/commands/InfoCommand.ts index c13f5dc..d55d0c4 100644 --- a/src/commands/InfoCommand.ts +++ b/src/commands/InfoCommand.ts @@ -1,6 +1,8 @@ import { Command } from '../base/command/Command'; import { ExtendedClient } from '../base/client/ExtendedClient'; -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { getGuild } from '../features/gameOffers/functions/getGuild'; +import { GuildChatInputCommandInteraction } from '../base/types/aliases'; export default class InfoCommand extends Command { public constructor(client: ExtendedClient) { @@ -27,7 +29,34 @@ export default class InfoCommand extends Command { }); } - public override async run(interaction: ChatInputCommandInteraction): Promise<void> { - await interaction.reply({ content: 'Hi' }); + public override async run(interaction: GuildChatInputCommandInteraction): Promise<void> { + const guildInfo = await getGuild(interaction.guildId); + + if (!guildInfo) { + await interaction.reply({ content: 'No settings have been found for this server. Please use the /configure channel command.' }); + return; + } + + const guild = await this.client.guilds.fetch(guildInfo.guild); + const channel = guildInfo.channel ? await this.client.channels.fetch(guildInfo.channel) : null; + + const createdAt = new Date(guildInfo.created_at); + const updatedAt = new Date(guildInfo.updated_at); + + const embed = new EmbedBuilder() + .setTitle('Free Games Notifier Settings') + .setFields( + { name: 'Server', value: guild.name, inline: true } as any, + { name: 'Subscription Channel', value: channel?.toString() ?? 'Unset', inline: true } as any, + { name: 'Subscriptions', value: "Here's a list of all the storefronts subscriptions for this server.", inline: false } as any, + ...Object.entries(guildInfo.storefronts).map(([storefront, enabled]) => { + return { name: storefront, value: enabled ? 'Enabled' : 'Disabled', inline: true }; + }) as any + ) + .setFooter({ + text: `Settings created @ ${createdAt.toLocaleString()}\nLast updated @ ${updatedAt.toLocaleString()}` + }); + + await interaction.reply({ embeds: [embed] }); } } From 8384a559ab5b5484c7d1bfe126a03309d59d2f95 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Tue, 6 Aug 2024 11:48:35 -0500 Subject: [PATCH 25/52] Created i18n utility. --- package-lock.json | 56 +++++++++++++++ package.json | 1 + src/i18n/error.spec.ts | 13 ++++ src/i18n/error.ts | 3 + src/i18n/strings/en.ts | 3 + src/i18n/strings/es.ts | 3 + src/i18n/strings/fr.ts | 3 + src/i18n/strings/index.ts | 11 +++ src/i18n/translate.spec.ts | 142 +++++++++++++++++++++++++++++++++++++ src/i18n/translate.ts | 60 ++++++++++++++++ 10 files changed, 295 insertions(+) create mode 100644 src/i18n/error.spec.ts create mode 100644 src/i18n/error.ts create mode 100644 src/i18n/strings/en.ts create mode 100644 src/i18n/strings/es.ts create mode 100644 src/i18n/strings/fr.ts create mode 100644 src/i18n/strings/index.ts create mode 100644 src/i18n/translate.spec.ts create mode 100644 src/i18n/translate.ts diff --git a/package-lock.json b/package-lock.json index d4328dc..4bcf7cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "discord.js": "^14.15.3", "dotenv": "^16.4.5", "flat": "^5.0.2", + "intl-messageformat": "^10.5.14", "pg-promise": "^11.9.1", "redis": "^4.7.0", "require-all": "^3.0.0" @@ -2395,6 +2396,50 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -6541,6 +6586,17 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "tslib": "^2.4.0" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", diff --git a/package.json b/package.json index 6a93e6b..f122419 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "discord.js": "^14.15.3", "dotenv": "^16.4.5", "flat": "^5.0.2", + "intl-messageformat": "^10.5.14", "pg-promise": "^11.9.1", "redis": "^4.7.0", "require-all": "^3.0.0" diff --git a/src/i18n/error.spec.ts b/src/i18n/error.spec.ts new file mode 100644 index 0000000..fd3db40 --- /dev/null +++ b/src/i18n/error.spec.ts @@ -0,0 +1,13 @@ +import { TranslatorError } from './error'; + +describe('i18n > Error', () => { + describe('class TranslatorError', () => { + it('should be defined.', () => { + expect(TranslatorError).toBeDefined(); + }); + + it('should return an instance of Error.', () => { + expect(new TranslatorError()).toBeInstanceOf(Error); + }); + }); +}); diff --git a/src/i18n/error.ts b/src/i18n/error.ts new file mode 100644 index 0000000..524473c --- /dev/null +++ b/src/i18n/error.ts @@ -0,0 +1,3 @@ +export class TranslatorError extends Error { + +} diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts new file mode 100644 index 0000000..6d916df --- /dev/null +++ b/src/i18n/strings/en.ts @@ -0,0 +1,3 @@ +export default { + +}; diff --git a/src/i18n/strings/es.ts b/src/i18n/strings/es.ts new file mode 100644 index 0000000..6d916df --- /dev/null +++ b/src/i18n/strings/es.ts @@ -0,0 +1,3 @@ +export default { + +}; diff --git a/src/i18n/strings/fr.ts b/src/i18n/strings/fr.ts new file mode 100644 index 0000000..6d916df --- /dev/null +++ b/src/i18n/strings/fr.ts @@ -0,0 +1,3 @@ +export default { + +}; diff --git a/src/i18n/strings/index.ts b/src/i18n/strings/index.ts new file mode 100644 index 0000000..0137645 --- /dev/null +++ b/src/i18n/strings/index.ts @@ -0,0 +1,11 @@ +import en from './en'; +import es from './es'; +import fr from './fr'; + +export default { + 'en-US': en, + 'en-GB': en, + 'es-ES': es, + 'es-419': es, + fr: fr +}; diff --git a/src/i18n/translate.spec.ts b/src/i18n/translate.spec.ts new file mode 100644 index 0000000..2708337 --- /dev/null +++ b/src/i18n/translate.spec.ts @@ -0,0 +1,142 @@ +import { getInteractionTranslator, MessageKey, translate, translateAll } from './translate'; +import { TranslatorError } from './error'; +import { BaseInteraction } from 'discord.js'; + +jest.mock('./strings/en', () => { + return { + hi: 'Hello', + bye: 'Goodbye {name}', + please: 'Please' + }; +}); + +jest.mock('./strings/es', () => { + return { + hi: 'Hola', + bye: 'Adios {name}', + please: 'Por favor' + }; +}); + +jest.mock('./strings/fr', () => { + return { + hi: 'Bonjour', + bye: 'Au revoir {name}' + }; +}); + +describe('i18n > Translate', () => { + describe('translate()', () => { + it('should be defined.', () => { + expect(translate).toBeDefined(); + }); + + it('should throw TranslatorError if locale does not exist.', () => { + const expectedError = new TranslatorError('No messages for locale ko exist.'); + + expect(() => { + translate('ko', 'hi' as MessageKey); + }).toThrow(expectedError); + }); + + it('should throw TranslatorError if message does not exist in given and default locale.', () => { + const expectedError = new TranslatorError('No message with key what for locale es-ES or en-US exists.'); + + expect(() => { + translate('es-ES', 'what' as MessageKey); + }).toThrow(expectedError); + }); + + it('should return expected message.', () => { + const result = translate('en-US', 'hi' as MessageKey); + expect(result).toBe('Hello'); + }); + + it('should return default message if it does not exist for given locale.', () => { + const result = translate('fr', 'please' as MessageKey); + expect(result).toBe('Please'); + }); + + it('should return expected message with values inserted.', () => { + const result = translate('en-US', 'bye' as MessageKey, { name: 'John' }); + expect(result).toBe('Goodbye John'); + }); + }); + + describe('getInteractionTranslator()', () => { + const interaction = { + locale: 'es-ES' + } as unknown as BaseInteraction; + + it('should be defined.', () => { + expect(getInteractionTranslator).toBeDefined(); + }); + + it('should throw TranslatorError if locale does not exist.', () => { + const expectedError = new TranslatorError('No messages for locale ko exist.'); + + expect(() => { + const t = getInteractionTranslator({ ...interaction, locale: 'ko' } as unknown as BaseInteraction); + t('hi' as MessageKey); + }).toThrow(expectedError); + }); + + it('should throw TranslatorError if message does not exist in given and default locale.', () => { + const expectedError = new TranslatorError('No message with key what for locale es-ES or en-US exists.'); + + expect(() => { + const t = getInteractionTranslator(interaction); + t('what' as MessageKey); + }).toThrow(expectedError); + }); + + it('should return expected message.', () => { + const t = getInteractionTranslator(interaction); + const result = t('hi' as MessageKey); + expect(result).toBe('Hola'); + }); + + it('should return default message if it does not exist for given locale.', () => { + const t = getInteractionTranslator({ ...interaction, locale: 'fr' } as unknown as BaseInteraction); + const result = t('please' as MessageKey); + expect(result).toBe('Please'); + }); + + it('should return expected message with values inserted.', () => { + const t = getInteractionTranslator(interaction); + const result = t('bye' as MessageKey, { name: 'John' }); + expect(result).toBe('Adios John'); + }); + }); + + describe('translateAll()', () => { + it('should be defined.', () => { + expect(translateAll).toBeDefined(); + }); + + it('should return an object with all messages for given key by locale.', () => { + const result = translateAll('hi' as MessageKey); + const expected = { + 'en-US': 'Hello', + 'en-GB': 'Hello', + 'es-ES': 'Hola', + 'es-419': 'Hola', + fr: 'Bonjour' + }; + + expect(result).toStrictEqual(expected); + }); + + it('should return an object with partial messages for given key by locale if exists.', () => { + const result = translateAll('please' as MessageKey); + const expected = { + 'en-US': 'Please', + 'en-GB': 'Please', + 'es-ES': 'Por favor', + 'es-419': 'Por favor' + }; + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/src/i18n/translate.ts b/src/i18n/translate.ts new file mode 100644 index 0000000..bea60ef --- /dev/null +++ b/src/i18n/translate.ts @@ -0,0 +1,60 @@ +import IntlMessageFormat, { FormatXMLElementFn, PrimitiveType } from 'intl-messageformat'; +import localeStrings from './strings'; +import { TranslatorError } from './error'; +import { BaseInteraction } from 'discord.js'; + +export type Locale = keyof typeof localeStrings | string; +export type MessageKey = keyof typeof localeStrings['en-US']; + +export type LocaleMessageMap = Record<Locale, Record<MessageKey, string | undefined> | undefined>; + +export type TranslateFunctionValues = Record<string, PrimitiveType | FormatXMLElementFn<string, string | string[]>>; +export type TranslateFunction = (locale: Locale, key: MessageKey, values?: TranslateFunctionValues) => string; +export type LocalizedTranslateFunction = (key: MessageKey, values?: TranslateFunctionValues) => string; +export type TranslateAllFunction = (key: MessageKey, values?: TranslateFunctionValues) => Partial<Record<Locale, string>> + +export const DEFAULT_LOCALE: Locale = 'en-US'; +const castLocaleStrings = localeStrings as LocaleMessageMap; + +const getMessage = (locale: Locale, key: MessageKey, useDefault: boolean = true): IntlMessageFormat => { + const messagesForLocale = castLocaleStrings[locale]; + if (!messagesForLocale) { + throw new TranslatorError(`No messages for locale ${locale} exist.`); + } + + const message = messagesForLocale[key]; + const defaultMessage = castLocaleStrings[DEFAULT_LOCALE]?.[key]; + const messageToReturn = useDefault ? message ?? defaultMessage : message; + + if (!messageToReturn) { + if (useDefault) { + throw new TranslatorError(`No message with key ${key} for locale ${locale} or ${DEFAULT_LOCALE} exists.`); + } + + throw new TranslatorError(`No message with key ${key} for locale ${locale} exists.`); + } + + return new IntlMessageFormat(messageToReturn); +}; + +export const translate: TranslateFunction = (locale, key, values) => { + return getMessage(locale, key, true).format(values as TranslateFunctionValues) as string; +}; + +export const getInteractionTranslator = (interaction: BaseInteraction): LocalizedTranslateFunction => (key, values) => { + return getMessage(interaction.locale, key, true).format(values as TranslateFunctionValues) as string; +}; + +export const translateAll: TranslateAllFunction = (key, values) => { + const locales = Object.keys(castLocaleStrings); + + return locales.reduce((obj, locale) => { + try { + obj[locale] = getMessage(locale, key, false).format(values as TranslateFunctionValues) as string; + return obj; + } catch (e) { + return obj; + } + }, {} as Partial<Record<Locale, string>>); +}; + From baad6ec6f27165155d8629f4764c164c2e8f2d74 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Tue, 6 Aug 2024 11:59:15 -0500 Subject: [PATCH 26/52] Added localized error message for commands. --- src/base/command/Command.spec.ts | 3 ++- src/base/command/Command.ts | 7 ++++--- src/i18n/strings/en.ts | 2 +- src/i18n/translate.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/base/command/Command.spec.ts b/src/base/command/Command.spec.ts index 7dba9c5..307107e 100644 --- a/src/base/command/Command.spec.ts +++ b/src/base/command/Command.spec.ts @@ -43,7 +43,8 @@ describe('Base > Command > Command', () => { const error = new Error('Oops!'); const interaction = { editReply: jest.fn(), - reply: jest.fn() + reply: jest.fn(), + locale: 'en-US' } as unknown as ChatInputCommandInteraction; it('should emit commandError with error.', async () => { diff --git a/src/base/command/Command.ts b/src/base/command/Command.ts index 8b8af13..82dc64a 100644 --- a/src/base/command/Command.ts +++ b/src/base/command/Command.ts @@ -1,5 +1,6 @@ import { ChatInputCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandsOnlyBuilder } from 'discord.js'; import { ExtendedClient } from '../client/ExtendedClient'; +import { getInteractionTranslator } from '../../i18n/translate'; export type AnySlashCommandBuilder = SlashCommandBuilder | SlashCommandOptionsOnlyBuilder | SlashCommandSubcommandsOnlyBuilder; @@ -26,13 +27,13 @@ export abstract class Command { public async onError(error: unknown, interaction: ChatInputCommandInteraction): Promise<void> { this.client.emit('commandError', error, this, interaction); - const message = `An error has occurred when running the command ${this.name}.`; + const t = getInteractionTranslator(interaction); if (interaction.deferred || interaction.replied) { - await interaction.editReply({ content: message }); + await interaction.editReply({ content: t('base.command.error.message', { name: this.name }) }); return; } - await interaction.reply({ content: message }); + await interaction.reply({ content: t('base.command.error.message', { name: this.name }) }); } } diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts index 6d916df..1cdabfa 100644 --- a/src/i18n/strings/en.ts +++ b/src/i18n/strings/en.ts @@ -1,3 +1,3 @@ export default { - + 'base.command.error.message': 'An error has occurred when running the command {name}.' }; diff --git a/src/i18n/translate.ts b/src/i18n/translate.ts index bea60ef..7c94748 100644 --- a/src/i18n/translate.ts +++ b/src/i18n/translate.ts @@ -6,7 +6,7 @@ import { BaseInteraction } from 'discord.js'; export type Locale = keyof typeof localeStrings | string; export type MessageKey = keyof typeof localeStrings['en-US']; -export type LocaleMessageMap = Record<Locale, Record<MessageKey, string | undefined> | undefined>; +export type LocaleMessageMap = Partial<Record<Locale, Partial<Record<MessageKey, string>>>>; export type TranslateFunctionValues = Record<string, PrimitiveType | FormatXMLElementFn<string, string | string[]>>; export type TranslateFunction = (locale: Locale, key: MessageKey, values?: TranslateFunctionValues) => string; From 093f3afaa832c747e5d0f460380d786bbd21108d Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Tue, 6 Aug 2024 13:37:15 -0500 Subject: [PATCH 27/52] Added localized texts in command builders. --- src/commands/ConfigureCommand.ts | 39 ++++++++++++++------------------ src/commands/HelpCommand.ts | 21 ++++------------- src/commands/InfoCommand.ts | 21 ++++------------- src/commands/OffersCommand.ts | 21 ++++------------- src/i18n/strings/en.ts | 20 +++++++++++++++- src/i18n/translate.spec.ts | 27 +++++++++++++++++++++- src/i18n/translate.ts | 4 ++++ 7 files changed, 81 insertions(+), 72 deletions(-) diff --git a/src/commands/ConfigureCommand.ts b/src/commands/ConfigureCommand.ts index fd60dfa..699decc 100644 --- a/src/commands/ConfigureCommand.ts +++ b/src/commands/ConfigureCommand.ts @@ -5,46 +5,41 @@ import { updateOrCreateGuildChannel } from '../features/gameOffers/functions/upd import { GuildChatInputCommandInteraction } from '../base/types/aliases'; import { getStorefronts } from '../features/gameOffers/functions/getStorefronts'; import { setGuildGameOfferEnabled } from '../features/gameOffers/functions/setGuildGameOfferEnabled'; +import { translateAll, translateDefault } from '../i18n/translate'; export default class ConfigureCommand extends Command { public constructor(client: ExtendedClient) { super(client, { name: 'configure', builder: new SlashCommandBuilder() - .setName('configure') - .setNameLocalizations({ - 'en-US': 'configure', - 'en-GB': 'configure', - 'es-ES': 'configurar', - 'es-419': 'configurar', - fr: 'configurer' - }) - .setDescription('Change the configuration for this server.') - .setDescriptionLocalizations({ - 'en-US': 'Change the configuration for this server.', - 'en-GB': 'Change the configuration for this server.', - 'es-ES': 'Cambia la configuración para este servidor.', - 'es-419': 'Cambia la configuración para este servidor.', - fr: 'Changez la configuration pour ce serveur.' - }) + .setName(translateDefault('commands.configure.name')) + .setNameLocalizations(translateAll('commands.configure.name')) + .setDescription(translateDefault('commands.configure.description')) + .setDescriptionLocalizations(translateAll('commands.configure.description')) .setDMPermission(false) .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) .addSubcommand((input) => { return input - .setName('channel') - .setDescription('Set the notifications channel.') + .setName(translateDefault('commands.configure.sub.channel.name')) + .setNameLocalizations(translateAll('commands.configure.sub.channel.name')) + .setDescription(translateDefault('commands.configure.sub.channel.description')) + .setDescriptionLocalizations(translateAll('commands.configure.sub.channel.description')) .addChannelOption((input) => { return input - .setName('channel') - .setDescription('The text channel to use.') + .setName(translateDefault('commands.configure.sub.channel.options.channel.name')) + .setNameLocalizations(translateAll('commands.configure.sub.channel.options.channel.name')) + .setDescription(translateDefault('commands.configure.sub.channel.options.channel.description')) + .setDescriptionLocalizations(translateAll('commands.configure.sub.channel.options.channel.description')) .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement) .setRequired(true); }); }) .addSubcommand((input) => { return input - .setName('storefronts') - .setDescription('Enable or disable notifications from specific Storefronts.'); + .setName(translateDefault('commands.configure.sub.storefronts.name')) + .setNameLocalizations(translateAll('commands.configure.sub.storefronts.name')) + .setDescription(translateDefault('commands.configure.sub.storefronts.description')) + .setDescriptionLocalizations(translateAll('commands.configure.sub.storefronts.description')); }) }); } diff --git a/src/commands/HelpCommand.ts b/src/commands/HelpCommand.ts index 2f2777b..d442e62 100644 --- a/src/commands/HelpCommand.ts +++ b/src/commands/HelpCommand.ts @@ -1,28 +1,17 @@ import { Command } from '../base/command/Command'; import { ExtendedClient } from '../base/client/ExtendedClient'; import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { translateAll, translateDefault } from '../i18n/translate'; export default class HelpCommand extends Command { public constructor(client: ExtendedClient) { super(client, { name: 'help', builder: new SlashCommandBuilder() - .setName('help') - .setNameLocalizations({ - 'en-US': 'help', - 'en-GB': 'help', - 'es-ES': 'ayuda', - 'es-419': 'ayuda', - fr: 'aide' - }) - .setDescription('Help and usage information.') - .setDescriptionLocalizations({ - 'en-US': 'Help and usage information.', - 'en-GB': 'Help and usage information.', - 'es-ES': 'Información de ayuda y utilización.', - 'es-419': 'Información de ayuda y utilización.', - fr: "Information d'aide et d'utilisation." - }) + .setName(translateDefault('commands.help.name')) + .setNameLocalizations(translateAll('commands.help.name')) + .setDescription(translateDefault('commands.help.description')) + .setDescriptionLocalizations(translateAll('commands.help.description')) }); } diff --git a/src/commands/InfoCommand.ts b/src/commands/InfoCommand.ts index d55d0c4..a335356 100644 --- a/src/commands/InfoCommand.ts +++ b/src/commands/InfoCommand.ts @@ -3,28 +3,17 @@ import { ExtendedClient } from '../base/client/ExtendedClient'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import { getGuild } from '../features/gameOffers/functions/getGuild'; import { GuildChatInputCommandInteraction } from '../base/types/aliases'; +import { translateAll, translateDefault } from '../i18n/translate'; export default class InfoCommand extends Command { public constructor(client: ExtendedClient) { super(client, { name: 'info', builder: new SlashCommandBuilder() - .setName('info') - .setNameLocalizations({ - 'en-US': 'info', - 'en-GB': 'info', - 'es-ES': 'info', - 'es-419': 'info', - fr: 'info' - }) - .setDescription('Get the settings information saved for this server.') - .setDescriptionLocalizations({ - 'en-US': 'Get the settings information saved for this server.', - 'en-GB': 'Get the settings information saved for this server.', - 'es-ES': 'Obten la información de configuración para este servidor.', - 'es-419': 'Obten la información de configuración para este servidor.', - fr: 'Obtenez la information de configuration pour ce serveur.' - }) + .setName(translateDefault('commands.info.name')) + .setNameLocalizations(translateAll('commands.info.name')) + .setDescription(translateDefault('commands.info.description')) + .setDescriptionLocalizations(translateAll('commands.info.description')) .setDMPermission(false) }); } diff --git a/src/commands/OffersCommand.ts b/src/commands/OffersCommand.ts index 87ea1ba..b366388 100644 --- a/src/commands/OffersCommand.ts +++ b/src/commands/OffersCommand.ts @@ -1,28 +1,17 @@ import { Command } from '../base/command/Command'; import { ExtendedClient } from '../base/client/ExtendedClient'; import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { translateAll, translateDefault } from '../i18n/translate'; export default class OffersCommand extends Command { public constructor(client: ExtendedClient) { super(client, { name: 'offers', builder: new SlashCommandBuilder() - .setName('offers') - .setNameLocalizations({ - 'en-US': 'offers', - 'en-GB': 'offers', - 'es-ES': 'ofertas', - 'es-419': 'ofertas', - fr: 'offres' - }) - .setDescription('Get a list of free games currently offered.') - .setDescriptionLocalizations({ - 'en-US': 'Get a list of free games currently offered.', - 'en-GB': 'Get a list of free games currently offered.', - 'es-ES': 'Obten una lista de los juegos gratis en oferta.', - 'es-419': 'Obten una lista de los juegos gratis en oferta.', - fr: 'Obtenez une liste des jeux offerts actuellement.' - }) + .setName(translateDefault('commands.offers.name')) + .setNameLocalizations(translateAll('commands.offers.name')) + .setDescription(translateDefault('commands.offers.description')) + .setDescriptionLocalizations(translateAll('commands.offers.description')) }); } diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts index 1cdabfa..ed00b93 100644 --- a/src/i18n/strings/en.ts +++ b/src/i18n/strings/en.ts @@ -1,3 +1,21 @@ export default { - 'base.command.error.message': 'An error has occurred when running the command {name}.' + 'base.command.error.message': 'An error has occurred when running the command {name}.', + + 'commands.configure.name': 'configure', + 'commands.configure.description': 'Change the configuration for this server.', + 'commands.configure.sub.channel.name': 'channel', + 'commands.configure.sub.channel.description': 'Change the notifications channel.', + 'commands.configure.sub.channel.options.channel.name': 'channel', + 'commands.configure.sub.channel.options.channel.description': 'The text channel to use.', + 'commands.configure.sub.storefronts.name': 'storefronts', + 'commands.configure.sub.storefronts.description': 'Enable or disable notifications from each storefront.', + + 'commands.help.name': 'help', + 'commands.help.description': 'Help and usage information.', + + 'commands.info.name': 'info', + 'commands.info.description': 'Get the settings information saved for this server.', + + 'commands.offers.name': 'offers', + 'commands.offers.description': 'Get a list of free games currently offered.' }; diff --git a/src/i18n/translate.spec.ts b/src/i18n/translate.spec.ts index 2708337..a282225 100644 --- a/src/i18n/translate.spec.ts +++ b/src/i18n/translate.spec.ts @@ -1,4 +1,4 @@ -import { getInteractionTranslator, MessageKey, translate, translateAll } from './translate'; +import { getInteractionTranslator, MessageKey, translate, translateAll, translateDefault } from './translate'; import { TranslatorError } from './error'; import { BaseInteraction } from 'discord.js'; @@ -63,6 +63,31 @@ describe('i18n > Translate', () => { }); }); + describe('translateDefault()', () => { + it('should be defined.', () => { + expect(translateDefault).toBeDefined(); + }); + + it('should throw TranslatorError if message does not exist in default locale.', () => { + const expectedError = new TranslatorError('No message with key what for locale en-US exists.'); + + expect(() => { + translateDefault('what' as MessageKey); + }).toThrow(expectedError); + }); + + it('should return expected message.', () => { + const result = translateDefault('hi' as MessageKey); + expect(result).toBe('Hello'); + }); + + it('should return expected message with values inserted.', () => { + const result = translateDefault('bye' as MessageKey, { name: 'John' }); + expect(result).toBe('Goodbye John'); + }); + }); + + describe('getInteractionTranslator()', () => { const interaction = { locale: 'es-ES' diff --git a/src/i18n/translate.ts b/src/i18n/translate.ts index 7c94748..6fcbb33 100644 --- a/src/i18n/translate.ts +++ b/src/i18n/translate.ts @@ -41,6 +41,10 @@ export const translate: TranslateFunction = (locale, key, values) => { return getMessage(locale, key, true).format(values as TranslateFunctionValues) as string; }; +export const translateDefault: LocalizedTranslateFunction = (key, values) => { + return getMessage(DEFAULT_LOCALE, key, false).format(values as TranslateFunctionValues) as string; +}; + export const getInteractionTranslator = (interaction: BaseInteraction): LocalizedTranslateFunction => (key, values) => { return getMessage(interaction.locale, key, true).format(values as TranslateFunctionValues) as string; }; From 756afe3d144549d8b968d314b75b94e8a6d812b3 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Wed, 7 Aug 2024 17:53:48 -0500 Subject: [PATCH 28/52] Implemented Help command. --- src/commands/HelpCommand.spec.ts | 54 ++++++++++++++++++++++++++++++++ src/commands/HelpCommand.ts | 29 +++++++++++++++-- src/config/constants.spec.ts | 23 ++++++++++++++ src/config/constants.ts | 6 ++++ src/i18n/strings/en.ts | 8 +++++ 5 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/config/constants.spec.ts create mode 100644 src/config/constants.ts diff --git a/src/commands/HelpCommand.spec.ts b/src/commands/HelpCommand.spec.ts index 445d0b6..9a81838 100644 --- a/src/commands/HelpCommand.spec.ts +++ b/src/commands/HelpCommand.spec.ts @@ -1,7 +1,61 @@ import HelpCommand from './HelpCommand'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { BOT_ISSUES_URL, BOT_WEBSITE_URL, CRAWLER_ISSUES_URL, MESSAGE_EMBED_COLOR, MESSAGE_EMBED_THUMBNAIL } from '../config/constants'; describe('Commands > HelpCommand', () => { + const client = {} as unknown as ExtendedClient; + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should be defined.', () => { expect(HelpCommand).toBeDefined(); }); + + describe('constructor', () => { + it('should set the appropriate values.', () => { + const command = new HelpCommand(client); + + expect(command.name).toBe('help'); + }); + }); + + describe('run()', () => { + const command = new HelpCommand(client); + const interaction = { + reply: jest.fn(), + locale: 'en-US' + } as unknown as ChatInputCommandInteraction; + + it('should reply with the embed.', async () => { + await command.run(interaction); + + const expectedEmbeds = [ + new EmbedBuilder() + .setTitle('Free Games Notifier') + .setColor(MESSAGE_EMBED_COLOR) + .setThumbnail(MESSAGE_EMBED_THUMBNAIL) + .setFields( + { name: 'Basic Information', value: 'This bot will send messages whenever a game becomes free on various game storefronts. To use this, make sure to run the /configure command to set the subscribed channel to send the notifications to. You may also choose which storefronts to get notified for.', inline: false }, + { name: 'Found a bug?', value: "If you found something broken, have feature requests or suggestions, don't hesitate to report them in the GitHub repo.", inline: false } + ) + ]; + + const expectedComponents = [ + new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder().setEmoji('🐛').setStyle(ButtonStyle.Link).setURL(BOT_ISSUES_URL).setLabel('Found a bug with the bot?'), + new ButtonBuilder().setEmoji('🐛').setStyle(ButtonStyle.Link).setURL(CRAWLER_ISSUES_URL).setLabel('Found a bug with game offer notifications?'), + ), + new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder().setEmoji('🌎').setStyle(ButtonStyle.Link).setURL(BOT_WEBSITE_URL).setLabel('Official Website'), + ) + ]; + + expect(interaction.reply).toHaveBeenCalledWith({ embeds: expectedEmbeds, components: expectedComponents }); + }); + }); }); diff --git a/src/commands/HelpCommand.ts b/src/commands/HelpCommand.ts index d442e62..64e57b8 100644 --- a/src/commands/HelpCommand.ts +++ b/src/commands/HelpCommand.ts @@ -1,7 +1,8 @@ import { Command } from '../base/command/Command'; import { ExtendedClient } from '../base/client/ExtendedClient'; -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { translateAll, translateDefault } from '../i18n/translate'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { getInteractionTranslator, translateAll, translateDefault } from '../i18n/translate'; +import { BOT_ISSUES_URL, BOT_WEBSITE_URL, CRAWLER_ISSUES_URL, MESSAGE_EMBED_COLOR, MESSAGE_EMBED_THUMBNAIL } from '../config/constants'; export default class HelpCommand extends Command { public constructor(client: ExtendedClient) { @@ -16,6 +17,28 @@ export default class HelpCommand extends Command { } public override async run(interaction: ChatInputCommandInteraction): Promise<void> { - await interaction.reply({ content: 'Hi' }); + const t = getInteractionTranslator(interaction); + + const embed = new EmbedBuilder() + .setTitle(t('commands.help.run.embed.title')) + .setColor(MESSAGE_EMBED_COLOR) + .setThumbnail(MESSAGE_EMBED_THUMBNAIL) + .setFields( + { name: t('commands.help.run.embed.fields.0.name'), value: t('commands.help.run.embed.fields.0.value'), inline: false }, + { name: t('commands.help.run.embed.fields.1.name'), value: t('commands.help.run.embed.fields.1.value'), inline: false } + ); + + const row1 = new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder().setEmoji('🐛').setStyle(ButtonStyle.Link).setURL(BOT_ISSUES_URL).setLabel(t('commands.help.run.buttons.bot_issues.label')), + new ButtonBuilder().setEmoji('🐛').setStyle(ButtonStyle.Link).setURL(CRAWLER_ISSUES_URL).setLabel(t('commands.help.run.buttons.crawler_issues.label')), + ); + + const row2 = new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder().setEmoji('🌎').setStyle(ButtonStyle.Link).setURL(BOT_WEBSITE_URL).setLabel(t('commands.help.run.buttons.bot_website.label')), + ); + + await interaction.reply({ embeds: [embed], components: [row1, row2] }); } } diff --git a/src/config/constants.spec.ts b/src/config/constants.spec.ts new file mode 100644 index 0000000..786df30 --- /dev/null +++ b/src/config/constants.spec.ts @@ -0,0 +1,23 @@ +import * as constants from './constants'; + +describe('Config > Constants', () => { + it('should export correct MESSAGE_EMBED_COLOR.', () => { + expect(constants.MESSAGE_EMBED_COLOR).toBe('#43aa8b'); + }); + + it('should export correct MESSAGE_EMBED_THUMBNAIL.', () => { + expect(constants.MESSAGE_EMBED_THUMBNAIL).toBe('https://i.imgur.com/Tqnk48j.png'); + }); + + it('should export correct BOT_ISSUES_URL.', () => { + expect(constants.BOT_ISSUES_URL).toBe('https://github.com/moonstar-x/discord-free-games-notifier/issues'); + }); + + it('should export correct CRAWLER_ISSUES_URL.', () => { + expect(constants.CRAWLER_ISSUES_URL).toBe('https://github.com/moonstar-x/free-games-crawler/issues'); + }); + + it('should export correct BOT_WEBSITE_URL.', () => { + expect(constants.BOT_WEBSITE_URL).toBe('https://docs.moonstar-x.dev/discord-free-games-notifier'); + }); +}); diff --git a/src/config/constants.ts b/src/config/constants.ts new file mode 100644 index 0000000..8d6683b --- /dev/null +++ b/src/config/constants.ts @@ -0,0 +1,6 @@ +export const MESSAGE_EMBED_COLOR = '#43aa8b'; +export const MESSAGE_EMBED_THUMBNAIL = 'https://i.imgur.com/Tqnk48j.png'; + +export const BOT_ISSUES_URL = 'https://github.com/moonstar-x/discord-free-games-notifier/issues'; +export const CRAWLER_ISSUES_URL = 'https://github.com/moonstar-x/free-games-crawler/issues'; +export const BOT_WEBSITE_URL = 'https://docs.moonstar-x.dev/discord-free-games-notifier'; diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts index ed00b93..b1dba21 100644 --- a/src/i18n/strings/en.ts +++ b/src/i18n/strings/en.ts @@ -12,6 +12,14 @@ export default { 'commands.help.name': 'help', 'commands.help.description': 'Help and usage information.', + 'commands.help.run.embed.title': 'Free Games Notifier', + 'commands.help.run.embed.fields.0.name': 'Basic Information', + 'commands.help.run.embed.fields.0.value': 'This bot will send messages whenever a game becomes free on various game storefronts. To use this, make sure to run the /configure command to set the subscribed channel to send the notifications to. You may also choose which storefronts to get notified for.', + 'commands.help.run.embed.fields.1.name': 'Found a bug?', + 'commands.help.run.embed.fields.1.value': "If you found something broken, have feature requests or suggestions, don't hesitate to report them in the GitHub repo.", + 'commands.help.run.buttons.bot_issues.label': 'Found a bug with the bot?', + 'commands.help.run.buttons.crawler_issues.label': 'Found a bug with game offer notifications?', + 'commands.help.run.buttons.bot_website.label': 'Official Website', 'commands.info.name': 'info', 'commands.info.description': 'Get the settings information saved for this server.', From 5303a58d4138012e37560e3e5632ee0134379317 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Wed, 7 Aug 2024 18:57:04 -0500 Subject: [PATCH 29/52] Implemented InfoCommand. --- src/commands/HelpCommand.spec.ts | 3 +- src/commands/InfoCommand.spec.ts | 145 +++++++++++++++++++++++++++++++ src/commands/InfoCommand.ts | 27 +++--- src/global.d.ts | 13 +++ src/i18n/strings/en.ts | 12 ++- 5 files changed, 185 insertions(+), 15 deletions(-) diff --git a/src/commands/HelpCommand.spec.ts b/src/commands/HelpCommand.spec.ts index 9a81838..911384a 100644 --- a/src/commands/HelpCommand.spec.ts +++ b/src/commands/HelpCommand.spec.ts @@ -17,7 +17,6 @@ describe('Commands > HelpCommand', () => { describe('constructor', () => { it('should set the appropriate values.', () => { const command = new HelpCommand(client); - expect(command.name).toBe('help'); }); }); @@ -38,7 +37,7 @@ describe('Commands > HelpCommand', () => { .setColor(MESSAGE_EMBED_COLOR) .setThumbnail(MESSAGE_EMBED_THUMBNAIL) .setFields( - { name: 'Basic Information', value: 'This bot will send messages whenever a game becomes free on various game storefronts. To use this, make sure to run the /configure command to set the subscribed channel to send the notifications to. You may also choose which storefronts to get notified for.', inline: false }, + { name: 'Basic Information', value: 'This bot will send messages whenever a game becomes free on various game storefronts. To use this, make sure to run the **/configure channel** command to set the subscribed channel to send the notifications to. You may also choose which storefronts to get notified for.', inline: false }, { name: 'Found a bug?', value: "If you found something broken, have feature requests or suggestions, don't hesitate to report them in the GitHub repo.", inline: false } ) ]; diff --git a/src/commands/InfoCommand.spec.ts b/src/commands/InfoCommand.spec.ts index 0e0e995..5c3aedf 100644 --- a/src/commands/InfoCommand.spec.ts +++ b/src/commands/InfoCommand.spec.ts @@ -1,7 +1,152 @@ import InfoCommand from './InfoCommand'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { GuildChatInputCommandInteraction } from '../base/types/aliases'; +import { getGuild } from '../features/gameOffers/functions/getGuild'; +import { EmbedBuilder } from 'discord.js'; +import { MESSAGE_EMBED_COLOR } from '../config/constants'; + +jest.mock('../features/gameOffers/functions/getGuild', () => { + return { + getGuild: jest.fn() + }; +}); describe('Commands > InfoCommand', () => { + const client = { + guilds: { + fetch: jest.fn().mockResolvedValue({ name: 'Guild' }) + }, + channels: { + fetch: jest.fn().mockResolvedValue('Channel') + } + } as unknown as ExtendedClient; + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should be defined.', () => { expect(InfoCommand).toBeDefined(); }); + + describe('constructor', () => { + it('should set the appropriate values.', () => { + const command = new InfoCommand(client); + expect(command.name).toBe('info'); + }); + }); + + describe('run()', () => { + const command = new InfoCommand(client); + const interaction = { + reply: jest.fn(), + locale: 'en-US', + guildId: '1267881983548063785' + } as unknown as GuildChatInputCommandInteraction; + + beforeAll(() => { + (getGuild as jest.Mock).mockResolvedValue({ + guild: '1267881983548063785', + channel: '1267881984642908346', + created_at: '2024-08-04T15:16:40.054841+00:00', + updated_at: '2024-08-04T15:16:40.054841+00:00', + storefronts: { + EpicGames: { + enabled: true + }, + Steam: { + enabled: false + } + } + }); + }); + + it('should reply with configure message if no settings are found.', async () => { + (getGuild as jest.Mock).mockResolvedValueOnce(null); + await command.run(interaction); + + expect(interaction.reply).toHaveBeenCalledWith({ content: 'No settings have been found for this server. Please use **/configure channel** command to set up the subscription channel.' }); + }); + + it('should reply with the correct embed if channel exists.', async () => { + await command.run(interaction); + + const expectedEmbeds = [ + new EmbedBuilder() + .setTitle('Settings for Free Games Notifier') + .setColor(MESSAGE_EMBED_COLOR) + .setFields( + { name: 'Server', value: 'Guild', inline: true }, + { name: 'Subscription Channel', value: 'Channel', inline: true }, + { name: 'Subscriptions', value: "Here's a list of all the storefronts this server is subscribed to.", inline: false }, + { name: 'EpicGames', value: '✅ Enabled', inline: true }, + { name: 'Steam', value: '❌ Disabled', inline: true } + ) + .setFooter({ + text: 'Created: 8/4/2024, 10:16:40 AM\nLast modified: 8/4/2024, 10:16:40 AM' + }) + ]; + + expect(interaction.reply).toHaveBeenCalledWith({ embeds: expectedEmbeds }); + }); + + it('should reply with the correct embed if no channel is set.', async () => { + (getGuild as jest.Mock).mockResolvedValueOnce({ + guild: '1267881983548063785', + channel: null, + created_at: '2024-08-04T15:16:40.054841+00:00', + updated_at: '2024-08-04T15:16:40.054841+00:00', + storefronts: { + EpicGames: { + enabled: true + }, + Steam: { + enabled: false + } + } + }); + await command.run(interaction); + + const expectedEmbeds = [ + new EmbedBuilder() + .setTitle('Settings for Free Games Notifier') + .setColor(MESSAGE_EMBED_COLOR) + .setFields( + { name: 'Server', value: 'Guild', inline: true }, + { name: 'Subscription Channel', value: 'Unset', inline: true }, + { name: 'Subscriptions', value: "Here's a list of all the storefronts this server is subscribed to.", inline: false }, + { name: 'EpicGames', value: '✅ Enabled', inline: true }, + { name: 'Steam', value: '❌ Disabled', inline: true } + ) + .setFooter({ + text: 'Created: 8/4/2024, 10:16:40 AM\nLast modified: 8/4/2024, 10:16:40 AM' + }) + ]; + + expect(interaction.reply).toHaveBeenCalledWith({ embeds: expectedEmbeds }); + }); + + it('should reply with the correct embed if channel set does not exist.', async () => { + (client.channels.fetch as jest.Mock).mockResolvedValueOnce(null); + await command.run(interaction); + + const expectedEmbeds = [ + new EmbedBuilder() + .setTitle('Settings for Free Games Notifier') + .setColor(MESSAGE_EMBED_COLOR) + .setFields( + { name: 'Server', value: 'Guild', inline: true }, + { name: 'Subscription Channel', value: 'Unset', inline: true }, + { name: 'Subscriptions', value: "Here's a list of all the storefronts this server is subscribed to.", inline: false }, + { name: 'EpicGames', value: '✅ Enabled', inline: true }, + { name: 'Steam', value: '❌ Disabled', inline: true } + ) + .setFooter({ + text: 'Created: 8/4/2024, 10:16:40 AM\nLast modified: 8/4/2024, 10:16:40 AM' + }) + ]; + + expect(interaction.reply).toHaveBeenCalledWith({ embeds: expectedEmbeds }); + }); + }); }); diff --git a/src/commands/InfoCommand.ts b/src/commands/InfoCommand.ts index a335356..bf64ffc 100644 --- a/src/commands/InfoCommand.ts +++ b/src/commands/InfoCommand.ts @@ -3,7 +3,8 @@ import { ExtendedClient } from '../base/client/ExtendedClient'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import { getGuild } from '../features/gameOffers/functions/getGuild'; import { GuildChatInputCommandInteraction } from '../base/types/aliases'; -import { translateAll, translateDefault } from '../i18n/translate'; +import { getInteractionTranslator, translateAll, translateDefault } from '../i18n/translate'; +import { MESSAGE_EMBED_COLOR } from '../config/constants'; export default class InfoCommand extends Command { public constructor(client: ExtendedClient) { @@ -19,31 +20,33 @@ export default class InfoCommand extends Command { } public override async run(interaction: GuildChatInputCommandInteraction): Promise<void> { + const t = getInteractionTranslator(interaction); const guildInfo = await getGuild(interaction.guildId); if (!guildInfo) { - await interaction.reply({ content: 'No settings have been found for this server. Please use the /configure channel command.' }); + await interaction.reply({ content: t('commands.info.run.pre_check.text') }); return; } const guild = await this.client.guilds.fetch(guildInfo.guild); const channel = guildInfo.channel ? await this.client.channels.fetch(guildInfo.channel) : null; - const createdAt = new Date(guildInfo.created_at); - const updatedAt = new Date(guildInfo.updated_at); + const createdAt = new Date(guildInfo.created_at).toLocaleString(interaction.locale); + const updatedAt = new Date(guildInfo.updated_at).toLocaleString(interaction.locale); const embed = new EmbedBuilder() - .setTitle('Free Games Notifier Settings') + .setTitle(t('commands.info.run.embed.title')) + .setColor(MESSAGE_EMBED_COLOR) .setFields( - { name: 'Server', value: guild.name, inline: true } as any, - { name: 'Subscription Channel', value: channel?.toString() ?? 'Unset', inline: true } as any, - { name: 'Subscriptions', value: "Here's a list of all the storefronts subscriptions for this server.", inline: false } as any, - ...Object.entries(guildInfo.storefronts).map(([storefront, enabled]) => { - return { name: storefront, value: enabled ? 'Enabled' : 'Disabled', inline: true }; - }) as any + { name: t('commands.info.run.embed.fields.server.name'), value: guild.name, inline: true }, + { name: t('commands.info.run.embed.fields.channel.name'), value: channel ? channel.toString() : t('commands.info.run.embed.fields.channel.value.unset'), inline: true }, + { name: t('commands.info.run.embed.fields.subscriptions.name'), value: t('commands.info.run.embed.fields.subscriptions.value'), inline: false }, + ...Object.entries(guildInfo.storefronts).map(([storefront, data]) => { + return { name: storefront, value: data.enabled ? t('commands.info.run.embed.fields.storefronts.value.enabled') : t('commands.info.run.embed.fields.storefronts.value.disabled'), inline: true }; + }) ) .setFooter({ - text: `Settings created @ ${createdAt.toLocaleString()}\nLast updated @ ${updatedAt.toLocaleString()}` + text: t('commands.info.run.embed.footer', { createdAt, updatedAt }) }); await interaction.reply({ embeds: [embed] }); diff --git a/src/global.d.ts b/src/global.d.ts index 7a70569..0d60fb8 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,3 +1,5 @@ +import { APIEmbedField } from 'discord-api-types/v10'; + declare global { namespace NodeJS { interface ProcessEnv { @@ -14,3 +16,14 @@ declare global { } } } + +// RestOrArray typing seems to not go along with Webstorm? +declare module 'discord.js' { + export class EmbedBuilder { + setFields(...fields: (APIEmbedField | APIEmbedField[])[]): this; + } + + export class ActionRowBuilder<T> { + setComponents(...fields: (T | T[])[]): this; + } +} diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts index b1dba21..25cdaf7 100644 --- a/src/i18n/strings/en.ts +++ b/src/i18n/strings/en.ts @@ -14,7 +14,7 @@ export default { 'commands.help.description': 'Help and usage information.', 'commands.help.run.embed.title': 'Free Games Notifier', 'commands.help.run.embed.fields.0.name': 'Basic Information', - 'commands.help.run.embed.fields.0.value': 'This bot will send messages whenever a game becomes free on various game storefronts. To use this, make sure to run the /configure command to set the subscribed channel to send the notifications to. You may also choose which storefronts to get notified for.', + 'commands.help.run.embed.fields.0.value': 'This bot will send messages whenever a game becomes free on various game storefronts. To use this, make sure to run the **/configure channel** command to set the subscribed channel to send the notifications to. You may also choose which storefronts to get notified for.', 'commands.help.run.embed.fields.1.name': 'Found a bug?', 'commands.help.run.embed.fields.1.value': "If you found something broken, have feature requests or suggestions, don't hesitate to report them in the GitHub repo.", 'commands.help.run.buttons.bot_issues.label': 'Found a bug with the bot?', @@ -23,6 +23,16 @@ export default { 'commands.info.name': 'info', 'commands.info.description': 'Get the settings information saved for this server.', + 'commands.info.run.pre_check.text': 'No settings have been found for this server. Please use **/configure channel** command to set up the subscription channel.', + 'commands.info.run.embed.title': 'Settings for Free Games Notifier', + 'commands.info.run.embed.fields.server.name': 'Server', + 'commands.info.run.embed.fields.channel.name': 'Subscription Channel', + 'commands.info.run.embed.fields.channel.value.unset': 'Unset', + 'commands.info.run.embed.fields.subscriptions.name': 'Subscriptions', + 'commands.info.run.embed.fields.subscriptions.value': "Here's a list of all the storefronts this server is subscribed to.", + 'commands.info.run.embed.fields.storefronts.value.enabled': '✅ Enabled', + 'commands.info.run.embed.fields.storefronts.value.disabled': '❌ Disabled', + 'commands.info.run.embed.footer': 'Created: {createdAt}\nLast modified: {updatedAt}', 'commands.offers.name': 'offers', 'commands.offers.description': 'Get a list of free games currently offered.' From 32568c6b939c5689db050bf5595dfc81cd7256cf Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Wed, 7 Aug 2024 20:27:32 -0500 Subject: [PATCH 30/52] Implemented OffersCommand. --- src/commands/InfoCommand.spec.ts | 32 ++++++------ src/commands/OffersCommand.spec.ts | 79 ++++++++++++++++++++++++++++++ src/commands/OffersCommand.ts | 21 +++++++- src/i18n/strings/en.ts | 16 +++++- src/i18n/translate.spec.ts | 44 ++++++++++++++++- src/i18n/translate.ts | 4 ++ src/models/gameOffer.spec.ts | 73 +++++++++++++++++++++++++++ src/models/gameOffer.ts | 34 +++++++++++++ 8 files changed, 281 insertions(+), 22 deletions(-) create mode 100644 src/models/gameOffer.spec.ts diff --git a/src/commands/InfoCommand.spec.ts b/src/commands/InfoCommand.spec.ts index 5c3aedf..ae971e3 100644 --- a/src/commands/InfoCommand.spec.ts +++ b/src/commands/InfoCommand.spec.ts @@ -7,7 +7,20 @@ import { MESSAGE_EMBED_COLOR } from '../config/constants'; jest.mock('../features/gameOffers/functions/getGuild', () => { return { - getGuild: jest.fn() + getGuild: jest.fn().mockResolvedValue({ + guild: '1267881983548063785', + channel: '1267881984642908346', + created_at: '2024-08-04T15:16:40.054841+00:00', + updated_at: '2024-08-04T15:16:40.054841+00:00', + storefronts: { + EpicGames: { + enabled: true + }, + Steam: { + enabled: false + } + } + }) }; }); @@ -44,23 +57,6 @@ describe('Commands > InfoCommand', () => { guildId: '1267881983548063785' } as unknown as GuildChatInputCommandInteraction; - beforeAll(() => { - (getGuild as jest.Mock).mockResolvedValue({ - guild: '1267881983548063785', - channel: '1267881984642908346', - created_at: '2024-08-04T15:16:40.054841+00:00', - updated_at: '2024-08-04T15:16:40.054841+00:00', - storefronts: { - EpicGames: { - enabled: true - }, - Steam: { - enabled: false - } - } - }); - }); - it('should reply with configure message if no settings are found.', async () => { (getGuild as jest.Mock).mockResolvedValueOnce(null); await command.run(interaction); diff --git a/src/commands/OffersCommand.spec.ts b/src/commands/OffersCommand.spec.ts index 6422001..f61db9b 100644 --- a/src/commands/OffersCommand.spec.ts +++ b/src/commands/OffersCommand.spec.ts @@ -1,7 +1,86 @@ import OffersCommand from './OffersCommand'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { getCurrentGameOffers } from '../features/gameOffers/functions/getCurrentGameOffers'; +import { MESSAGE_EMBED_COLOR } from '../config/constants'; + +jest.mock('../features/gameOffers/functions/getCurrentGameOffers', () => { + return { + getCurrentGameOffers: jest.fn().mockResolvedValue([{ + storefront: 'Steam', + id: '442070', + url: 'https://store.steampowered.com/app/442070/Drawful_2/?snr=1_7_7_2300_150_1', + title: 'Drawful 2', + description: 'For 3-8 players and an audience of thousands! Your phones or tablets are your controllers! The game of terrible drawings and hilariously wrong answers.', + type: 'game', + publisher: 'Jackbox Games', + original_price: 5.79, + original_price_fmt: '$5.79 USD', + thumbnail: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/442070/header.jpg?t=1721927113' + }]) + }; +}); + +jest.mock('../features/gameOffers/functions/getStorefronts', () => { + return { + getStorefronts: jest.fn().mockResolvedValue(['Steam', 'EpicGames']) + }; +}); describe('Commands > OffersCommand', () => { + const client = {} as ExtendedClient; + it('should be defined.', () => { expect(OffersCommand).toBeDefined(); }); + + describe('constructor', () => { + it('should set the appropriate values.', () => { + const command = new OffersCommand(client); + expect(command.name).toBe('offers'); + }); + }); + + describe('run()', () => { + const command = new OffersCommand(client); + const interaction = { + reply: jest.fn(), + followUp: jest.fn(), + locale: 'en-US' + } as unknown as ChatInputCommandInteraction; + + it('should reply with the empty message if no offers are available.', async () => { + (getCurrentGameOffers as jest.Mock).mockResolvedValueOnce([]); + await command.run(interaction); + + expect(interaction.reply).toHaveBeenCalledWith({ content: 'There are currently no offers in any of the following storefronts: Steam, EpicGames.' }); + }); + + it('should reply with start message.', async () => { + await command.run(interaction); + expect(interaction.reply).toHaveBeenCalledWith({ content: "Here's a list of the currently available offers." }); + }); + + it('should send an embed and component for each game.', async () => { + const expectedEmbed = new EmbedBuilder() + .setTitle('Drawful 2 on Steam') + .setDescription('For 3-8 players and an audience of thousands! Your phones or tablets are your controllers! The game of terrible drawings and hilariously wrong answers.',) + .setURL('https://store.steampowered.com/app/442070/Drawful_2/?snr=1_7_7_2300_150_1') + .setColor(MESSAGE_EMBED_COLOR) + .setFields( + { name: 'Type', value: '👾️ Game', inline: true }, + { name: 'Publisher', value: 'Jackbox Games', inline: true }, + { name: 'Original Price', value: '$5.79 USD', inline: true }, + ) + .setImage('https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/442070/header.jpg?t=1721927113'); + + const expectedComponent = new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder().setEmoji('🕹️').setStyle(ButtonStyle.Link).setURL('https://store.steampowered.com/app/442070/Drawful_2/?snr=1_7_7_2300_150_1').setLabel('Get it on Steam!') + ); + + await command.run(interaction); + expect(interaction.followUp).toHaveBeenCalledWith({ embeds: [expectedEmbed], components: [expectedComponent] }); + }); + }); }); diff --git a/src/commands/OffersCommand.ts b/src/commands/OffersCommand.ts index b366388..b6e1b53 100644 --- a/src/commands/OffersCommand.ts +++ b/src/commands/OffersCommand.ts @@ -1,7 +1,10 @@ import { Command } from '../base/command/Command'; import { ExtendedClient } from '../base/client/ExtendedClient'; import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { translateAll, translateDefault } from '../i18n/translate'; +import { getInteractionTranslator, translateAll, translateDefault } from '../i18n/translate'; +import { getCurrentGameOffers } from '../features/gameOffers/functions/getCurrentGameOffers'; +import { getStorefronts } from '../features/gameOffers/functions/getStorefronts'; +import { offerToMessage } from '../models/gameOffer'; export default class OffersCommand extends Command { public constructor(client: ExtendedClient) { @@ -16,6 +19,20 @@ export default class OffersCommand extends Command { } public override async run(interaction: ChatInputCommandInteraction): Promise<void> { - await interaction.reply({ content: 'Hi' }); + const t = getInteractionTranslator(interaction); + const offers = await getCurrentGameOffers(); + + if (!offers.length) { + const storefronts = await getStorefronts(); + await interaction.reply({ content: t('commands.offers.run.empty.text', { list: storefronts.join(', ') }) }); + return; + } + + await interaction.reply({ content: t('commands.offers.run.start.text') }); + + for (const offer of offers) { + const { embed, component } = offerToMessage(offer, t); + await interaction.followUp({ embeds: [embed], components: [component] }); + } } } diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts index 25cdaf7..246e73a 100644 --- a/src/i18n/strings/en.ts +++ b/src/i18n/strings/en.ts @@ -35,5 +35,19 @@ export default { 'commands.info.run.embed.footer': 'Created: {createdAt}\nLast modified: {updatedAt}', 'commands.offers.name': 'offers', - 'commands.offers.description': 'Get a list of free games currently offered.' + 'commands.offers.description': 'Get a list of free games currently offered.', + 'commands.offers.run.empty.text': 'There are currently no offers in any of the following storefronts: {list}.', + 'commands.offers.run.start.text': "Here's a list of the currently available offers.", + + 'offers.embed.title': '{game} on {storefront}', + 'offers.embed.fields.publisher.name': 'Publisher', + 'offers.embed.fields.publisher.name.unknown': 'N/A', + 'offers.embed.fields.price.name': 'Original Price', + 'offers.embed.fields.price.name.unknown': 'N/A', + 'offers.embed.fields.type.name': 'Type', + 'offers.embed.fields.type.value.game': '👾️ Game', + 'offers.embed.fields.type.value.dlc': '➕ DLC', + 'offers.embed.fields.type.value.bundle': '💰 Bundle', + 'offers.embed.fields.type.value.other': '❓ Other', + 'offers.buttons.get.label': 'Get it on {storefront}!' }; diff --git a/src/i18n/translate.spec.ts b/src/i18n/translate.spec.ts index a282225..3074b5e 100644 --- a/src/i18n/translate.spec.ts +++ b/src/i18n/translate.spec.ts @@ -1,4 +1,4 @@ -import { getInteractionTranslator, MessageKey, translate, translateAll, translateDefault } from './translate'; +import { getInteractionTranslator, getTranslator, MessageKey, translate, translateAll, translateDefault } from './translate'; import { TranslatorError } from './error'; import { BaseInteraction } from 'discord.js'; @@ -134,6 +134,48 @@ describe('i18n > Translate', () => { }); }); + describe('getTranslator()', () => { + it('should be defined.', () => { + expect(getTranslator).toBeDefined(); + }); + + it('should throw TranslatorError if locale does not exist.', () => { + const expectedError = new TranslatorError('No messages for locale ko exist.'); + + expect(() => { + const t = getTranslator('ko'); + t('hi' as MessageKey); + }).toThrow(expectedError); + }); + + it('should throw TranslatorError if message does not exist in given and default locale.', () => { + const expectedError = new TranslatorError('No message with key what for locale es-ES or en-US exists.'); + + expect(() => { + const t = getTranslator('es-ES'); + t('what' as MessageKey); + }).toThrow(expectedError); + }); + + it('should return expected message.', () => { + const t = getTranslator('es-ES'); + const result = t('hi' as MessageKey); + expect(result).toBe('Hola'); + }); + + it('should return default message if it does not exist for given locale.', () => { + const t = getTranslator('fr'); + const result = t('please' as MessageKey); + expect(result).toBe('Please'); + }); + + it('should return expected message with values inserted.', () => { + const t = getTranslator('es-ES'); + const result = t('bye' as MessageKey, { name: 'John' }); + expect(result).toBe('Adios John'); + }); + }); + describe('translateAll()', () => { it('should be defined.', () => { expect(translateAll).toBeDefined(); diff --git a/src/i18n/translate.ts b/src/i18n/translate.ts index 6fcbb33..204ce62 100644 --- a/src/i18n/translate.ts +++ b/src/i18n/translate.ts @@ -49,6 +49,10 @@ export const getInteractionTranslator = (interaction: BaseInteraction): Localize return getMessage(interaction.locale, key, true).format(values as TranslateFunctionValues) as string; }; +export const getTranslator = (locale: Locale): LocalizedTranslateFunction => (key, values) => { + return getMessage(locale, key, true).format(values as TranslateFunctionValues) as string; +}; + export const translateAll: TranslateAllFunction = (key, values) => { const locales = Object.keys(castLocaleStrings); diff --git a/src/models/gameOffer.spec.ts b/src/models/gameOffer.spec.ts new file mode 100644 index 0000000..3b76ab1 --- /dev/null +++ b/src/models/gameOffer.spec.ts @@ -0,0 +1,73 @@ +import { GameOffer, offerToMessage } from './gameOffer'; +import { getTranslator } from '../i18n/translate'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js'; +import { MESSAGE_EMBED_COLOR } from '../config/constants'; + +describe('Models > GameOffer', () => { + describe('offerToMessage()', () => { + const t = getTranslator('en-US'); + const offer: GameOffer = { + storefront: 'Steam', + id: '442070', + url: 'https://store.steampowered.com/app/442070/Drawful_2/?snr=1_7_7_2300_150_1', + title: 'Drawful 2', + description: 'For 3-8 players and an audience of thousands! Your phones or tablets are your controllers! The game of terrible drawings and hilariously wrong answers.', + type: 'game', + publisher: 'Jackbox Games', + original_price: 5.79, + original_price_fmt: '$5.79 USD', + thumbnail: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/442070/header.jpg?t=1721927113' + }; + + it('should be defined.', () => { + expect(offerToMessage).toBeDefined(); + }); + + it('should return the correct embed when all values are provided.', () => { + const { embed } = offerToMessage(offer, t); + + const expectedEmbed = new EmbedBuilder() + .setTitle('Drawful 2 on Steam') + .setDescription(offer.description) + .setURL(offer.url) + .setColor(MESSAGE_EMBED_COLOR) + .setFields( + { name: 'Type', value: '👾️ Game', inline: true }, + { name: 'Publisher', value: offer.publisher!, inline: true }, + { name: 'Original Price', value: offer.original_price_fmt!, inline: true }, + ) + .setImage(offer.thumbnail); + + expect(embed).toStrictEqual(expectedEmbed); + }); + + it('should return the correct embed with default values if missing.', () => { + const missingOffer: GameOffer = { ...offer, type: 'other', publisher: null, original_price_fmt: null, thumbnail: null }; + const { embed } = offerToMessage(missingOffer, t); + + const expectedEmbed = new EmbedBuilder() + .setTitle('Drawful 2 on Steam') + .setDescription(offer.description) + .setURL(offer.url) + .setColor(MESSAGE_EMBED_COLOR) + .setFields( + { name: 'Type', value: '❓ Other', inline: true }, + { name: 'Publisher', value: 'N/A', inline: true }, + { name: 'Original Price', value: 'N/A', inline: true }, + ); + + expect(embed).toStrictEqual(expectedEmbed); + }); + + it('should return the correct component.', () => { + const { component } = offerToMessage(offer, t); + + const expectedComponent = new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder().setEmoji('🕹️').setStyle(ButtonStyle.Link).setURL(offer.url).setLabel('Get it on Steam!') + ); + + expect(component).toStrictEqual(expectedComponent); + }); + }); +}); diff --git a/src/models/gameOffer.ts b/src/models/gameOffer.ts index c1e0350..de35880 100644 --- a/src/models/gameOffer.ts +++ b/src/models/gameOffer.ts @@ -1,3 +1,7 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js'; +import { LocalizedTranslateFunction } from '../i18n/translate'; +import { MESSAGE_EMBED_COLOR } from '../config/constants'; + export type GameOfferType = 'game' | 'dlc' | 'bundle' | 'other'; export interface GameOffer { @@ -24,3 +28,33 @@ export interface GameOfferGuild { } } } + +export const offerToMessage = (offer: GameOffer, t: LocalizedTranslateFunction): { embed: EmbedBuilder, component: ActionRowBuilder<ButtonBuilder> } => { + const typeMap: Partial<Record<GameOfferType, string>> = { + game: t('offers.embed.fields.type.value.game'), + dlc: t('offers.embed.fields.type.value.dlc'), + bundle: t('offers.embed.fields.type.value.bundle') + }; + + const embed = new EmbedBuilder() + .setTitle(t('offers.embed.title', { game: offer.title, storefront: offer.storefront })) + .setDescription(offer.description) + .setURL(offer.url) + .setColor(MESSAGE_EMBED_COLOR) + .setFields( + { name: t('offers.embed.fields.type.name'), value: typeMap[offer.type] || t('offers.embed.fields.type.value.other'), inline: true }, + { name: t('offers.embed.fields.publisher.name'), value: offer.publisher || t('offers.embed.fields.publisher.name.unknown'), inline: true }, + { name: t('offers.embed.fields.price.name'), value: offer.original_price_fmt || t('offers.embed.fields.price.name.unknown'), inline: true }, + ); + + if (offer.thumbnail) { + embed.setImage(offer.thumbnail); + } + + const component = new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder().setEmoji('🕹️').setStyle(ButtonStyle.Link).setURL(offer.url).setLabel(t('offers.buttons.get.label', { storefront: offer.storefront })) + ); + + return { embed, component }; +}; From fdbe39a8d255e3ff1eccd808fe507cc77c83ef22 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Thu, 8 Aug 2024 00:49:35 -0500 Subject: [PATCH 31/52] Added locale as part of guild settings. --- migrations/001-init.sql | 16 +++++++++++ src/commands/ConfigureCommand.ts | 13 +++++++++ src/commands/InfoCommand.spec.ts | 4 +++ src/commands/InfoCommand.ts | 4 ++- .../gameOffers/functions/getGuild.spec.ts | 1 + .../updateOrCreateGuildLocale.spec.ts | 27 +++++++++++++++++++ .../functions/updateOrCreateGuildLocale.ts | 7 +++++ src/i18n/strings/en.ts | 9 ++++++- src/i18n/translate.ts | 7 +++++ src/models/gameOffer.ts | 3 ++- 10 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/features/gameOffers/functions/updateOrCreateGuildLocale.spec.ts create mode 100644 src/features/gameOffers/functions/updateOrCreateGuildLocale.ts diff --git a/migrations/001-init.sql b/migrations/001-init.sql index 56ad0e9..f7e7f96 100644 --- a/migrations/001-init.sql +++ b/migrations/001-init.sql @@ -1,6 +1,7 @@ CREATE TABLE IF NOT EXISTS GuildSettings( guild VARCHAR(64) NOT NULL, channel VARCHAR(64), + locale VARCHAR(16) DEFAULT 'en-US', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT pk_GuildSettings @@ -51,6 +52,7 @@ BEGIN SELECT json_build_object( 'guild', GS.guild, 'channel', GS.channel, + 'locale', COALESCE(GS.locale, 'en-US'), 'created_at', GS.created_at, 'updated_at', GS.updated_at, 'storefronts', ( @@ -86,6 +88,20 @@ BEGIN END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION update_or_create_guild_locale(guild_id VARCHAR, new_locale VARCHAR) +RETURNS VOID AS $$ +BEGIN + UPDATE GuildSettings + SET locale = new_locale, + updated_at = now() + WHERE guild = guild_id; + + IF NOT FOUND THEN + INSERT INTO GuildSettings(guild, locale) VALUES(guild_id, new_locale); + END IF; +END; +$$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION set_guild_game_offer_enabled(guild_id VARCHAR, storefront_name VARCHAR, new_enabled BOOL) RETURNS VOID AS $$ DECLARE diff --git a/src/commands/ConfigureCommand.ts b/src/commands/ConfigureCommand.ts index 699decc..780be13 100644 --- a/src/commands/ConfigureCommand.ts +++ b/src/commands/ConfigureCommand.ts @@ -41,6 +41,13 @@ export default class ConfigureCommand extends Command { .setDescription(translateDefault('commands.configure.sub.storefronts.description')) .setDescriptionLocalizations(translateAll('commands.configure.sub.storefronts.description')); }) + .addSubcommand((input) => { + return input + .setName(translateDefault('commands.configure.sub.language.name')) + .setNameLocalizations(translateAll('commands.configure.sub.language.name')) + .setDescription(translateDefault('commands.configure.sub.language.description')) + .setDescriptionLocalizations(translateAll('commands.configure.sub.language.description')); + }) }); } @@ -52,6 +59,8 @@ export default class ConfigureCommand extends Command { return this.runChannel(interaction); case 'storefronts': return this.runStorefronts(interaction); + case 'language': + return this.runLanguage(interaction); default: return this.runDefault(interaction); } @@ -103,6 +112,10 @@ export default class ConfigureCommand extends Command { } } + private async runLanguage(interaction: GuildChatInputCommandInteraction): Promise<void> { + await interaction.reply({ content: 'hi' }); + } + private async runDefault(interaction: ChatInputCommandInteraction): Promise<void> { await interaction.reply({ content: 'Unknown subcommand received.' }); } diff --git a/src/commands/InfoCommand.spec.ts b/src/commands/InfoCommand.spec.ts index ae971e3..1084cec 100644 --- a/src/commands/InfoCommand.spec.ts +++ b/src/commands/InfoCommand.spec.ts @@ -10,6 +10,7 @@ jest.mock('../features/gameOffers/functions/getGuild', () => { getGuild: jest.fn().mockResolvedValue({ guild: '1267881983548063785', channel: '1267881984642908346', + locale: 'en-US', created_at: '2024-08-04T15:16:40.054841+00:00', updated_at: '2024-08-04T15:16:40.054841+00:00', storefronts: { @@ -74,6 +75,7 @@ describe('Commands > InfoCommand', () => { .setFields( { name: 'Server', value: 'Guild', inline: true }, { name: 'Subscription Channel', value: 'Channel', inline: true }, + { name: 'Subscription Language', value: 'English', inline: true }, { name: 'Subscriptions', value: "Here's a list of all the storefronts this server is subscribed to.", inline: false }, { name: 'EpicGames', value: '✅ Enabled', inline: true }, { name: 'Steam', value: '❌ Disabled', inline: true } @@ -110,6 +112,7 @@ describe('Commands > InfoCommand', () => { .setFields( { name: 'Server', value: 'Guild', inline: true }, { name: 'Subscription Channel', value: 'Unset', inline: true }, + { name: 'Subscription Language', value: 'English', inline: true }, { name: 'Subscriptions', value: "Here's a list of all the storefronts this server is subscribed to.", inline: false }, { name: 'EpicGames', value: '✅ Enabled', inline: true }, { name: 'Steam', value: '❌ Disabled', inline: true } @@ -133,6 +136,7 @@ describe('Commands > InfoCommand', () => { .setFields( { name: 'Server', value: 'Guild', inline: true }, { name: 'Subscription Channel', value: 'Unset', inline: true }, + { name: 'Subscription Language', value: 'English', inline: true }, { name: 'Subscriptions', value: "Here's a list of all the storefronts this server is subscribed to.", inline: false }, { name: 'EpicGames', value: '✅ Enabled', inline: true }, { name: 'Steam', value: '❌ Disabled', inline: true } diff --git a/src/commands/InfoCommand.ts b/src/commands/InfoCommand.ts index bf64ffc..634f51c 100644 --- a/src/commands/InfoCommand.ts +++ b/src/commands/InfoCommand.ts @@ -3,7 +3,7 @@ import { ExtendedClient } from '../base/client/ExtendedClient'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import { getGuild } from '../features/gameOffers/functions/getGuild'; import { GuildChatInputCommandInteraction } from '../base/types/aliases'; -import { getInteractionTranslator, translateAll, translateDefault } from '../i18n/translate'; +import { AVAILABLE_LOCALES, DEFAULT_LOCALE, getInteractionTranslator, translateAll, translateDefault } from '../i18n/translate'; import { MESSAGE_EMBED_COLOR } from '../config/constants'; export default class InfoCommand extends Command { @@ -30,6 +30,7 @@ export default class InfoCommand extends Command { const guild = await this.client.guilds.fetch(guildInfo.guild); const channel = guildInfo.channel ? await this.client.channels.fetch(guildInfo.channel) : null; + const localeKey = AVAILABLE_LOCALES[guildInfo.locale] ?? AVAILABLE_LOCALES[DEFAULT_LOCALE]; const createdAt = new Date(guildInfo.created_at).toLocaleString(interaction.locale); const updatedAt = new Date(guildInfo.updated_at).toLocaleString(interaction.locale); @@ -40,6 +41,7 @@ export default class InfoCommand extends Command { .setFields( { name: t('commands.info.run.embed.fields.server.name'), value: guild.name, inline: true }, { name: t('commands.info.run.embed.fields.channel.name'), value: channel ? channel.toString() : t('commands.info.run.embed.fields.channel.value.unset'), inline: true }, + { name: t('commands.info.run.embed.fields.locale.name'), value: t(localeKey), inline: true }, { name: t('commands.info.run.embed.fields.subscriptions.name'), value: t('commands.info.run.embed.fields.subscriptions.value'), inline: false }, ...Object.entries(guildInfo.storefronts).map(([storefront, data]) => { return { name: storefront, value: data.enabled ? t('commands.info.run.embed.fields.storefronts.value.enabled') : t('commands.info.run.embed.fields.storefronts.value.disabled'), inline: true }; diff --git a/src/features/gameOffers/functions/getGuild.spec.ts b/src/features/gameOffers/functions/getGuild.spec.ts index d31cd44..eb8ae51 100644 --- a/src/features/gameOffers/functions/getGuild.spec.ts +++ b/src/features/gameOffers/functions/getGuild.spec.ts @@ -8,6 +8,7 @@ describe('Features > GameOffers > Functions > GetGuild', () => { const guildData = { guild: '1267881983548063785', channel: null, + locale: 'en-US', created_at: '2024-08-04T15:16:40.054841+00:00', updated_at: '2024-08-04T15:16:40.054841+00:00', storefronts: { diff --git a/src/features/gameOffers/functions/updateOrCreateGuildLocale.spec.ts b/src/features/gameOffers/functions/updateOrCreateGuildLocale.spec.ts new file mode 100644 index 0000000..232c6b6 --- /dev/null +++ b/src/features/gameOffers/functions/updateOrCreateGuildLocale.spec.ts @@ -0,0 +1,27 @@ +import { updateOrCreateGuildLocale } from './updateOrCreateGuildLocale'; +import { db } from '../../../services/database/client'; +import { Locale } from '../../../i18n/translate'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > UpdateOrCreateGuildLocale', () => { + const guildId = '1267881983548063785'; + const locale: Locale = 'en-US'; + + beforeAll(() => { + (db.any as jest.Mock).mockImplementation(() => Promise.resolve()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(updateOrCreateGuildLocale).toBeDefined(); + }); + + it('should return undefined.', async () => { + const result = await updateOrCreateGuildLocale(guildId, locale); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/features/gameOffers/functions/updateOrCreateGuildLocale.ts b/src/features/gameOffers/functions/updateOrCreateGuildLocale.ts new file mode 100644 index 0000000..82fb52e --- /dev/null +++ b/src/features/gameOffers/functions/updateOrCreateGuildLocale.ts @@ -0,0 +1,7 @@ +import { db } from '../../../services/database/client'; +import { Locale } from '../../../i18n/translate'; + +export const updateOrCreateGuildLocale = async (guildId: string, locale: Locale): Promise<void> => { + const query = 'SELECT update_or_create_guild_locale($1, $2)'; + await db.any(query, [guildId, locale]); +}; diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts index 246e73a..a55c7a6 100644 --- a/src/i18n/strings/en.ts +++ b/src/i18n/strings/en.ts @@ -9,6 +9,8 @@ export default { 'commands.configure.sub.channel.options.channel.description': 'The text channel to use.', 'commands.configure.sub.storefronts.name': 'storefronts', 'commands.configure.sub.storefronts.description': 'Enable or disable notifications from each storefront.', + 'commands.configure.sub.language.name': 'language', + 'commands.configure.sub.language.description': 'Change the language for the notifications.', 'commands.help.name': 'help', 'commands.help.description': 'Help and usage information.', @@ -28,6 +30,7 @@ export default { 'commands.info.run.embed.fields.server.name': 'Server', 'commands.info.run.embed.fields.channel.name': 'Subscription Channel', 'commands.info.run.embed.fields.channel.value.unset': 'Unset', + 'commands.info.run.embed.fields.locale.name': 'Subscription Language', 'commands.info.run.embed.fields.subscriptions.name': 'Subscriptions', 'commands.info.run.embed.fields.subscriptions.value': "Here's a list of all the storefronts this server is subscribed to.", 'commands.info.run.embed.fields.storefronts.value.enabled': '✅ Enabled', @@ -49,5 +52,9 @@ export default { 'offers.embed.fields.type.value.dlc': '➕ DLC', 'offers.embed.fields.type.value.bundle': '💰 Bundle', 'offers.embed.fields.type.value.other': '❓ Other', - 'offers.buttons.get.label': 'Get it on {storefront}!' + 'offers.buttons.get.label': 'Get it on {storefront}!', + + 'locales.names.english': 'English', + 'locales.names.spanish': 'Spanish', + 'locales.names.french': 'French' }; diff --git a/src/i18n/translate.ts b/src/i18n/translate.ts index 204ce62..c25dfa9 100644 --- a/src/i18n/translate.ts +++ b/src/i18n/translate.ts @@ -14,6 +14,13 @@ export type LocalizedTranslateFunction = (key: MessageKey, values?: TranslateFun export type TranslateAllFunction = (key: MessageKey, values?: TranslateFunctionValues) => Partial<Record<Locale, string>> export const DEFAULT_LOCALE: Locale = 'en-US'; +export const AVAILABLE_LOCALES: Record<Locale, MessageKey> = { + 'en-US': 'locales.names.english', + 'en-GB': 'locales.names.english', + 'es-ES': 'locales.names.spanish', + 'es-419': 'locales.names.spanish', + fr: 'locales.names.french' +}; const castLocaleStrings = localeStrings as LocaleMessageMap; const getMessage = (locale: Locale, key: MessageKey, useDefault: boolean = true): IntlMessageFormat => { diff --git a/src/models/gameOffer.ts b/src/models/gameOffer.ts index de35880..466bbd0 100644 --- a/src/models/gameOffer.ts +++ b/src/models/gameOffer.ts @@ -1,5 +1,5 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js'; -import { LocalizedTranslateFunction } from '../i18n/translate'; +import { Locale, LocalizedTranslateFunction } from '../i18n/translate'; import { MESSAGE_EMBED_COLOR } from '../config/constants'; export type GameOfferType = 'game' | 'dlc' | 'bundle' | 'other'; @@ -20,6 +20,7 @@ export interface GameOffer { export interface GameOfferGuild { guild: string channel: string | null + locale: Locale created_at: string updated_at: string storefronts: { From 12cfe1d992d88e7152203ce885699d46f12b69ae Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Thu, 8 Aug 2024 12:45:09 -0500 Subject: [PATCH 32/52] Implemented ConfigureCommand. --- src/commands/ConfigureCommand.spec.ts | 208 ++++++++++++++++++++++++++ src/commands/ConfigureCommand.ts | 103 +++++++++---- src/i18n/strings/en.ts | 14 ++ src/i18n/translate.ts | 5 + 4 files changed, 302 insertions(+), 28 deletions(-) diff --git a/src/commands/ConfigureCommand.spec.ts b/src/commands/ConfigureCommand.spec.ts index 745ec35..55cfe66 100644 --- a/src/commands/ConfigureCommand.spec.ts +++ b/src/commands/ConfigureCommand.spec.ts @@ -1,7 +1,215 @@ import ConfigureCommand from './ConfigureCommand'; +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { GuildChatInputCommandInteraction } from '../base/types/aliases'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { updateOrCreateGuildChannel } from '../features/gameOffers/functions/updateOrCreateGuildChannel'; +import { getStorefronts } from '../features/gameOffers/functions/getStorefronts'; +import { setGuildGameOfferEnabled } from '../features/gameOffers/functions/setGuildGameOfferEnabled'; +import { updateOrCreateGuildLocale } from '../features/gameOffers/functions/updateOrCreateGuildLocale'; + +jest.mock('../features/gameOffers/functions/updateOrCreateGuildChannel', () => { + return { + updateOrCreateGuildChannel: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + +jest.mock('../features/gameOffers/functions/getStorefronts', () => { + return { + getStorefronts: jest.fn().mockResolvedValue(['EpicGames', 'Steam']) + }; +}); + +jest.mock('../features/gameOffers/functions/setGuildGameOfferEnabled', () => { + return { + setGuildGameOfferEnabled: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + +jest.mock('../features/gameOffers/functions/updateOrCreateGuildLocale', () => { + return { + updateOrCreateGuildLocale: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); describe('Commands > ConfigureCommand', () => { + const client = {} as ExtendedClient; + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should be defined.', () => { expect(ConfigureCommand).toBeDefined(); }); + + describe('constructor', () => { + it('should set the appropriate values.', () => { + const command = new ConfigureCommand(client); + expect(command.name).toBe('configure'); + }); + }); + + describe('run()', () => { + const command = new ConfigureCommand(client); + + describe('runChannel()', () => { + const interaction = { + reply: jest.fn(), + locale: 'en-US', + guildId: '1267881983548063785', + options: { + getSubcommand: jest.fn().mockReturnValue('channel'), + getChannel: jest.fn().mockReturnValue({ id: 'channel', toString: () => 'Channel' }) + } + } as unknown as GuildChatInputCommandInteraction; + + it('should reply with pre check message if no channel is provided.', async () => { + (interaction.options.getChannel as jest.Mock).mockReturnValueOnce(null); + await command.run(interaction); + + expect(interaction.reply).toHaveBeenCalledWith({ content: 'No channel provided.' }); + }); + + it('should update guild channel.', async () => { + await command.run(interaction); + expect(updateOrCreateGuildChannel).toHaveBeenCalledWith(interaction.guildId, 'channel'); + }); + + it('should reply with channel update message.', async () => { + await command.run(interaction); + expect(interaction.reply).toHaveBeenCalledWith({ content: 'Successfully updated notifications channel to Channel.' }); + }); + }); + + describe('runStorefronts()', () => { + const userResponseMock = { + customId: 'configure-storefronts-enable', + update: jest.fn().mockImplementation(() => Promise.resolve()) + }; + const followUpResponseMock = { + awaitMessageComponent: jest.fn().mockResolvedValue(userResponseMock) + }; + const interaction = { + reply: jest.fn(), + followUp: jest.fn().mockResolvedValue(followUpResponseMock), + locale: 'en-US', + guildId: '1267881983548063785', + options: { + getSubcommand: jest.fn().mockReturnValue('storefronts') + } + } as unknown as GuildChatInputCommandInteraction; + + it('should reply with empty storefronts message if no storefronts exist.', async () => { + (getStorefronts as jest.Mock).mockResolvedValueOnce([]); + await command.run(interaction); + + expect(interaction.reply).toHaveBeenCalledWith({ content: 'No storefronts are available right now.' }); + }); + + it('should reply with start message.', async () => { + await command.run(interaction); + expect(interaction.reply).toHaveBeenCalledWith({ content: 'You will receive a follow up for each storefront available. Please, click on the buttons as they appear to enable or disable notifications for each storefront.' }); + }); + + it('should send follow up with correct components for each storefront.', async () => { + const expectedComponents = [ + new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder() + .setCustomId('configure-storefronts-enable') + .setLabel('Enable') + .setStyle(ButtonStyle.Primary) + .setEmoji('✅'), + new ButtonBuilder() + .setCustomId('configure-storefronts-disable') + .setLabel('Disable') + .setStyle(ButtonStyle.Secondary) + .setEmoji('❌') + ) + ]; + + await command.run(interaction); + + expect(interaction.followUp).toHaveBeenCalledWith({ components: expectedComponents, content: 'Would you like to receive notifications for **EpicGames**?' }); + expect(interaction.followUp).toHaveBeenCalledWith({ components: expectedComponents, content: 'Would you like to receive notifications for **Steam**?' }); + }); + + it('should set the guild offer enabled from user response if enabled clicked.', async () => { + userResponseMock.customId = 'configure-storefronts-enable'; + await command.run(interaction); + + expect(setGuildGameOfferEnabled).toHaveBeenCalledWith(interaction.guildId, 'EpicGames', true); + expect(setGuildGameOfferEnabled).toHaveBeenCalledWith(interaction.guildId, 'Steam', true); + }); + + it('should set the guild offer enabled from user response if disabled clicked.', async () => { + userResponseMock.customId = 'configure-storefronts-disable'; + await command.run(interaction); + + expect(setGuildGameOfferEnabled).toHaveBeenCalledWith(interaction.guildId, 'EpicGames', false); + expect(setGuildGameOfferEnabled).toHaveBeenCalledWith(interaction.guildId, 'Steam', false); + }); + + it('should update the response with correct message if enabled clicked.', async () => { + userResponseMock.customId = 'configure-storefronts-enable'; + await command.run(interaction); + + expect(userResponseMock.update).toHaveBeenCalledWith({ components: [], content: 'This server will receive notifications for **EpicGames**.' }); + expect(userResponseMock.update).toHaveBeenCalledWith({ components: [], content: 'This server will receive notifications for **Steam**.' }); + }); + + it('should update the response with correct message if disabled clicked.', async () => { + userResponseMock.customId = 'configure-storefronts-disable'; + await command.run(interaction); + + expect(userResponseMock.update).toHaveBeenCalledWith({ components: [], content: 'This server will no longer receive notifications for **EpicGames**.' }); + expect(userResponseMock.update).toHaveBeenCalledWith({ components: [], content: 'This server will no longer receive notifications for **Steam**.' }); + }); + }); + + describe('runLanguage()', () => { + const interaction = { + reply: jest.fn(), + locale: 'en-US', + guildId: '1267881983548063785', + options: { + getSubcommand: jest.fn().mockReturnValue('language'), + getString: jest.fn().mockReturnValue('en-US') + } + } as unknown as GuildChatInputCommandInteraction; + + it('should reply with pre check message if no locale is provided.', async () => { + (interaction.options.getString as jest.Mock).mockReturnValueOnce(null); + await command.run(interaction); + + expect(interaction.reply).toHaveBeenCalledWith({ content: 'No language provided.' }); + }); + + it('should update guild locale.', async () => { + await command.run(interaction); + expect(updateOrCreateGuildLocale).toHaveBeenCalledWith(interaction.guildId, 'en-US'); + }); + + it('should reply with language update message.', async () => { + await command.run(interaction); + expect(interaction.reply).toHaveBeenCalledWith({ content: 'Successfully updated notifications language to **English**.' }); + }); + }); + + describe('runDefault()', () => { + const interaction = { + reply: jest.fn(), + locale: 'en-US', + guildId: '1267881983548063785', + options: { + getSubcommand: jest.fn().mockReturnValue('unknown') + } + } as unknown as GuildChatInputCommandInteraction; + + it('should reply with unknown subcommand message.', async () => { + await command.run(interaction); + expect(interaction.reply).toHaveBeenCalledWith({ content: 'Unknown subcommand received.' }); + }); + }); + }); }); diff --git a/src/commands/ConfigureCommand.ts b/src/commands/ConfigureCommand.ts index 780be13..65e0aa8 100644 --- a/src/commands/ConfigureCommand.ts +++ b/src/commands/ConfigureCommand.ts @@ -5,7 +5,8 @@ import { updateOrCreateGuildChannel } from '../features/gameOffers/functions/upd import { GuildChatInputCommandInteraction } from '../base/types/aliases'; import { getStorefronts } from '../features/gameOffers/functions/getStorefronts'; import { setGuildGameOfferEnabled } from '../features/gameOffers/functions/setGuildGameOfferEnabled'; -import { translateAll, translateDefault } from '../i18n/translate'; +import { AVAILABLE_LOCALES, getInteractionTranslator, Locale, MESSAGE_KEY_TO_LOCALE, MessageKey, translateAll, translateDefault } from '../i18n/translate'; +import { updateOrCreateGuildLocale } from '../features/gameOffers/functions/updateOrCreateGuildLocale'; export default class ConfigureCommand extends Command { public constructor(client: ExtendedClient) { @@ -46,7 +47,22 @@ export default class ConfigureCommand extends Command { .setName(translateDefault('commands.configure.sub.language.name')) .setNameLocalizations(translateAll('commands.configure.sub.language.name')) .setDescription(translateDefault('commands.configure.sub.language.description')) - .setDescriptionLocalizations(translateAll('commands.configure.sub.language.description')); + .setDescriptionLocalizations(translateAll('commands.configure.sub.language.description')) + .addStringOption((input) => { + return input + .setName(translateDefault('commands.configure.sub.language.options.language.name')) + .setNameLocalizations(translateAll('commands.configure.sub.language.options.language.name')) + .setDescription(translateDefault('commands.configure.sub.language.options.language.description')) + .setDescriptionLocalizations(translateAll('commands.configure.sub.language.options.language.description')) + .setRequired(true) + .setChoices(Object.entries(MESSAGE_KEY_TO_LOCALE).map(([key, locale]) => { + return { + name: translateDefault(key as MessageKey), + name_localizations: translateAll(key as MessageKey), + value: locale + }; + })); + }); }) }); } @@ -67,56 +83,87 @@ export default class ConfigureCommand extends Command { } private async runChannel(interaction: GuildChatInputCommandInteraction): Promise<void> { + const t = getInteractionTranslator(interaction); const channel = interaction.options.getChannel('channel'); if (!channel) { - await interaction.reply({ content: 'No channel received.' }); + await interaction.reply({ content: t('commands.configure.run.channel.pre_check.text') }); return; } await updateOrCreateGuildChannel(interaction.guildId, channel.id); - await interaction.reply({ content: `Updated notifications channel to ${channel}.` }); + await interaction.reply({ content: t('commands.configure.run.channel.success.text', { channel: channel.toString() }) }); } private async runStorefronts(interaction: GuildChatInputCommandInteraction): Promise<void> { + const t = getInteractionTranslator(interaction); const storefronts = await getStorefronts(); if (!storefronts.length) { - await interaction.reply({ content: 'No storefronts are available.' }); + await interaction.reply({ content: t('commands.configure.run.storefronts.empty.text') }); return; } - await interaction.reply({ content: 'Click on the enable or disable buttons for each Storefront to enable or disable it.' }); - - for (const storefront of storefronts) { - const enableButton = new ButtonBuilder() - .setCustomId('storefronts-enable') - .setLabel('Enable') - .setStyle(ButtonStyle.Primary); - - const disableButton = new ButtonBuilder() - .setCustomId('storefronts-disable') - .setLabel('Disable') - .setStyle(ButtonStyle.Secondary); - - const actionRow = new ActionRowBuilder() - .addComponents(enableButton, disableButton); - - const response = await interaction.followUp({ content: `Enable or disable **${storefront}**?`, components: [actionRow] as any }); - const confirmation = await response.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id && i.customId.startsWith('storefronts'), time: 60_000 }); + await interaction.reply({ content: t('commands.configure.run.storefronts.start.text') }); - const enabled = confirmation.customId === 'storefronts-enable'; - await setGuildGameOfferEnabled(interaction.guildId, storefront, enabled); + const buttonIds = { + enable: 'configure-storefronts-enable', + disable: 'configure-storefronts-disable' + }; - await confirmation.update({ content: `Updated ${storefront} subscription.`, components: [] }); + for (const storefront of storefronts) { + const row = new ActionRowBuilder<ButtonBuilder>() + .setComponents( + new ButtonBuilder() + .setCustomId(buttonIds.enable) + .setLabel(t('commands.configure.run.storefronts.buttons.enable.label')) + .setStyle(ButtonStyle.Primary) + .setEmoji('✅'), + new ButtonBuilder() + .setCustomId(buttonIds.disable) + .setLabel(t('commands.configure.run.storefronts.buttons.disable.label')) + .setStyle(ButtonStyle.Secondary) + .setEmoji('❌') + ); + + const followUpSent = await interaction.followUp({ content: t('commands.configure.run.storefronts.follow_up.question.text', { storefront }), components: [row] }); + const userResponse = await followUpSent.awaitMessageComponent({ + filter: (i) => { + return i.user.id === interaction.user.id && Object.values(buttonIds).includes(i.customId); + }, + time: 60_000 + }); + + const shouldEnable = userResponse.customId === buttonIds.enable; + await setGuildGameOfferEnabled(interaction.guildId, storefront, shouldEnable); + + await userResponse.update({ + content: shouldEnable ? + t('commands.configure.run.storefronts.response.update.positive.text', { storefront }) : + t('commands.configure.run.storefronts.response.update.negative.text', { storefront }), + components: [] + }); } } private async runLanguage(interaction: GuildChatInputCommandInteraction): Promise<void> { - await interaction.reply({ content: 'hi' }); + const t = getInteractionTranslator(interaction); + const locale = interaction.options.getString('language') as Locale | null; + + if (!locale) { + await interaction.reply({ content: t('commands.configure.run.language.pre_check.text') }); + return; + } + + const language = t(AVAILABLE_LOCALES[locale]); + await updateOrCreateGuildLocale(interaction.guildId, locale); + + await interaction.reply({ content: t('commands.configure.run.language.success.text', { language }) }); } private async runDefault(interaction: ChatInputCommandInteraction): Promise<void> { - await interaction.reply({ content: 'Unknown subcommand received.' }); + const t = getInteractionTranslator(interaction); + + await interaction.reply({ content: t('commands.configure.run.default.response.text') }); } } diff --git a/src/i18n/strings/en.ts b/src/i18n/strings/en.ts index a55c7a6..c223000 100644 --- a/src/i18n/strings/en.ts +++ b/src/i18n/strings/en.ts @@ -11,6 +11,20 @@ export default { 'commands.configure.sub.storefronts.description': 'Enable or disable notifications from each storefront.', 'commands.configure.sub.language.name': 'language', 'commands.configure.sub.language.description': 'Change the language for the notifications.', + 'commands.configure.sub.language.options.language.name': 'language', + 'commands.configure.sub.language.options.language.description': 'The language in which to send the notifications.', + 'commands.configure.run.channel.pre_check.text': 'No channel provided.', + 'commands.configure.run.channel.success.text': 'Successfully updated notifications channel to {channel}.', + 'commands.configure.run.storefronts.empty.text': 'No storefronts are available right now.', + 'commands.configure.run.storefronts.start.text': 'You will receive a follow up for each storefront available. Please, click on the buttons as they appear to enable or disable notifications for each storefront.', + 'commands.configure.run.storefronts.buttons.enable.label': 'Enable', + 'commands.configure.run.storefronts.buttons.disable.label': 'Disable', + 'commands.configure.run.storefronts.follow_up.question.text': 'Would you like to receive notifications for **{storefront}**?', + 'commands.configure.run.storefronts.response.update.positive.text': 'This server will receive notifications for **{storefront}**.', + 'commands.configure.run.storefronts.response.update.negative.text': 'This server will no longer receive notifications for **{storefront}**.', + 'commands.configure.run.language.pre_check.text': 'No language provided.', + 'commands.configure.run.language.success.text': 'Successfully updated notifications language to **{language}**.', + 'commands.configure.run.default.response.text': 'Unknown subcommand received.', 'commands.help.name': 'help', 'commands.help.description': 'Help and usage information.', diff --git a/src/i18n/translate.ts b/src/i18n/translate.ts index c25dfa9..66b7a34 100644 --- a/src/i18n/translate.ts +++ b/src/i18n/translate.ts @@ -21,6 +21,11 @@ export const AVAILABLE_LOCALES: Record<Locale, MessageKey> = { 'es-419': 'locales.names.spanish', fr: 'locales.names.french' }; +export const MESSAGE_KEY_TO_LOCALE: Partial<Record<MessageKey, Locale>> = { + 'locales.names.english': 'en-US', + 'locales.names.spanish': 'es-419', + 'locales.names.french': 'fr' +}; const castLocaleStrings = localeStrings as LocaleMessageMap; const getMessage = (locale: Locale, key: MessageKey, useDefault: boolean = true): IntlMessageFormat => { From 0fc26a860c99b79f9888fa34bbbd7a0d97083914 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Thu, 8 Aug 2024 13:10:55 -0500 Subject: [PATCH 33/52] Created getNotifiableGuilds service. --- migrations/001-init.sql | 20 +++++++++++ .../functions/getNotifiableGuilds.spec.ts | 36 +++++++++++++++++++ .../functions/getNotifiableGuilds.ts | 9 +++++ src/models/gameOffer.ts | 6 ++++ 4 files changed, 71 insertions(+) create mode 100644 src/features/gameOffers/functions/getNotifiableGuilds.spec.ts create mode 100644 src/features/gameOffers/functions/getNotifiableGuilds.ts diff --git a/migrations/001-init.sql b/migrations/001-init.sql index f7e7f96..06f8f95 100644 --- a/migrations/001-init.sql +++ b/migrations/001-init.sql @@ -134,3 +134,23 @@ BEGIN DELETE FROM GuildSettings WHERE guild = guild_id; END; $$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION get_notifiable_guilds(storefront VARCHAR) +RETURNS JSON AS $$ +BEGIN + RETURN ( + SELECT COALESCE( + json_agg( + json_build_object( + 'guild', GS.guild, + 'channel', GS.channel, + 'locale', COALESCE(GS.locale, 'en-US') + ) + ), + '[]'::json) FROM GuildSettings GS + INNER JOIN GuildGameOffersEnabled GGOE ON GS.guild = GGOE.guild + INNER JOIN GameOfferStorefront GOS ON GGOE.storefront_id = GOS.id + WHERE GS.channel IS NOT NULL AND GOS.name = storefront AND GGOE.enabled + ); +END; +$$ LANGUAGE plpgsql; diff --git a/src/features/gameOffers/functions/getNotifiableGuilds.spec.ts b/src/features/gameOffers/functions/getNotifiableGuilds.spec.ts new file mode 100644 index 0000000..0bfcf80 --- /dev/null +++ b/src/features/gameOffers/functions/getNotifiableGuilds.spec.ts @@ -0,0 +1,36 @@ +import { getNotifiableGuilds } from './getNotifiableGuilds'; +import { db } from '../../../services/database/client'; + +jest.mock('../../../services/database/client'); + +describe('Features > GameOffers > Functions > GetNotifiableGuilds', () => { + const guildData = { + guild: '1267881983548063785', + channel: null, + locale: 'en-US' + }; + + beforeAll(() => { + (db.one as jest.Mock).mockResolvedValue({ result: guildData }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined.', () => { + expect(getNotifiableGuilds).toBeDefined(); + }); + + it('should return the result.', async () => { + const result = await getNotifiableGuilds('Steam'); + expect(result).toBe(guildData); + }); + + it('should return null if not found result.', async () => { + (db.one as jest.Mock).mockResolvedValueOnce({ result: [] }); + const result = await getNotifiableGuilds('Steam'); + + expect(result).toStrictEqual([]); + }); +}); diff --git a/src/features/gameOffers/functions/getNotifiableGuilds.ts b/src/features/gameOffers/functions/getNotifiableGuilds.ts new file mode 100644 index 0000000..8247260 --- /dev/null +++ b/src/features/gameOffers/functions/getNotifiableGuilds.ts @@ -0,0 +1,9 @@ +import { db } from '../../../services/database/client'; +import { NotifiableGuild } from '../../../models/gameOffer'; + +export const getNotifiableGuilds = async (storefront: string): Promise<NotifiableGuild[]> => { + const query = 'SELECT get_notifiable_guilds($1) AS result'; + const result = await db.one<{ result: NotifiableGuild[] }>(query, [storefront]); + + return result.result; +}; diff --git a/src/models/gameOffer.ts b/src/models/gameOffer.ts index 466bbd0..63c43fc 100644 --- a/src/models/gameOffer.ts +++ b/src/models/gameOffer.ts @@ -30,6 +30,12 @@ export interface GameOfferGuild { } } +export interface NotifiableGuild { + guild: string + channel: string + locale: Locale +} + export const offerToMessage = (offer: GameOffer, t: LocalizedTranslateFunction): { embed: EmbedBuilder, component: ActionRowBuilder<ButtonBuilder> } => { const typeMap: Partial<Record<GameOfferType, string>> = { game: t('offers.embed.fields.type.value.game'), From 5633bdfd7a57dfb4708f52ff65c09c1c96687f43 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Thu, 8 Aug 2024 15:21:15 -0500 Subject: [PATCH 34/52] Implemented subscribeToOffers service. --- .../pubsub/subscribeToOffers.spec.ts | 58 +++++++++++++++++++ .../gameOffers/pubsub/subscribeToOffers.ts | 27 +++++++++ src/services/redis/__mocks__/client.ts | 4 +- 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/features/gameOffers/pubsub/subscribeToOffers.spec.ts create mode 100644 src/features/gameOffers/pubsub/subscribeToOffers.ts diff --git a/src/features/gameOffers/pubsub/subscribeToOffers.spec.ts b/src/features/gameOffers/pubsub/subscribeToOffers.spec.ts new file mode 100644 index 0000000..e27d8c4 --- /dev/null +++ b/src/features/gameOffers/pubsub/subscribeToOffers.spec.ts @@ -0,0 +1,58 @@ +import { subscribeToOffers } from './subscribeToOffers'; +import { createRedisClient } from '../../../services/redis/client'; +import logger from '@moonstar-x/logger'; + +jest.mock('../../../services/redis/client'); +jest.mock('@moonstar-x/logger'); + +describe('Features > GameOffers > PubSub > SubscribeToOffers', () => { + const client = createRedisClient(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + (client.subscribe as jest.Mock).mockReset(); + }); + + describe('subscribeToOffers()', () => { + const listener = jest.fn(); + + it('should be defined.', () => { + expect(subscribeToOffers).toBeDefined(); + }); + + it('should return a unsubscribe function.', async () => { + const unsubscribe = await subscribeToOffers(listener); + await unsubscribe(); + + expect(unsubscribe).toBeInstanceOf(Function); + expect(client.disconnect).toHaveBeenCalled(); + expect(client.unsubscribe).toHaveBeenCalledWith('offers', expect.anything()); + }); + + it('should subscribe to offers channel.', async () => { + (client.subscribe as jest.Mock).mockImplementationOnce((_channel, fn) => { + fn('{"data": 123}'); + return Promise.resolve(); + }); + await subscribeToOffers(listener); + + expect(client.subscribe).toHaveBeenCalledWith('offers', expect.anything()); + expect(listener).toHaveBeenCalledWith({ data: 123 }); + }); + + it('should log error if subscribed value is invalid.', async () => { + (client.subscribe as jest.Mock).mockImplementationOnce((_channel, fn) => { + fn('invalid data'); + return Promise.resolve(); + }); + await subscribeToOffers(listener); + + expect(listener).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledWith('Received invalid offer in subscription.'); + expect(logger.error).toHaveBeenCalledWith('invalid data'); + }); + }); +}); diff --git a/src/features/gameOffers/pubsub/subscribeToOffers.ts b/src/features/gameOffers/pubsub/subscribeToOffers.ts new file mode 100644 index 0000000..88b0e45 --- /dev/null +++ b/src/features/gameOffers/pubsub/subscribeToOffers.ts @@ -0,0 +1,27 @@ +import { createRedisClient } from '../../../services/redis/client'; +import logger from '@moonstar-x/logger'; +import { GameOffer } from '../../../models/gameOffer'; + +export type OffersSubscriberFunction = (offer: GameOffer) => void; +export type UnsubscribeFunction = () => Promise<void>; + +export const subscribeToOffers = async (fn: OffersSubscriberFunction): Promise<UnsubscribeFunction> => { + const client = createRedisClient(); + await client.connect(); + + const listener = (data: string) => { + try { + const parsed: GameOffer = JSON.parse(data); + fn(parsed); + } catch (error) { + logger.error('Received invalid offer in subscription.'); + logger.error(data); + } + }; + + await client.subscribe('offers', listener); + return async () => { + await client.unsubscribe('offers', listener); + await client.disconnect(); + }; +}; diff --git a/src/services/redis/__mocks__/client.ts b/src/services/redis/__mocks__/client.ts index d65a061..e9d2f5d 100644 --- a/src/services/redis/__mocks__/client.ts +++ b/src/services/redis/__mocks__/client.ts @@ -2,7 +2,9 @@ const client = { connect: jest.fn(), disconnect: jest.fn(), keys: jest.fn(), - mGet: jest.fn() + mGet: jest.fn(), + subscribe: jest.fn(), + unsubscribe: jest.fn() }; export const createRedisClient = jest.fn().mockReturnValue(client); From 44ce3cd9fa9b8d51c2c3f4b5678d982d0de43591 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Thu, 8 Aug 2024 15:21:44 -0500 Subject: [PATCH 35/52] Created base for OffersNotifier. --- src/base/client/ExtendedClient.spec.ts | 17 ++++ src/base/client/ExtendedClient.ts | 4 + .../gameOffers/classes/OffersNotifier.spec.ts | 81 +++++++++++++++++++ .../gameOffers/classes/OffersNotifier.ts | 27 +++++++ 4 files changed, 129 insertions(+) create mode 100644 src/features/gameOffers/classes/OffersNotifier.spec.ts create mode 100644 src/features/gameOffers/classes/OffersNotifier.ts diff --git a/src/base/client/ExtendedClient.spec.ts b/src/base/client/ExtendedClient.spec.ts index 6a722da..822a24f 100644 --- a/src/base/client/ExtendedClient.spec.ts +++ b/src/base/client/ExtendedClient.spec.ts @@ -11,6 +11,16 @@ jest.mock('../command/InteractionDispatcher', () => { }; }); +jest.mock('../../features/gameOffers/classes/OffersNotifier', () => { + return { + OffersNotifier: jest.fn().mockImplementation(() => { + return { + subscribe: jest.fn() + }; + }) + }; +}); + describe('Base > Client > ExtendedClient', () => { beforeEach(() => { jest.clearAllMocks(); @@ -29,6 +39,13 @@ describe('Base > Client > ExtendedClient', () => { client.emit('interactionCreate', interaction); expect(client.dispatcher.handleInteraction).toHaveBeenCalledWith(interaction); }); + + it('should register ready on construction.', () => { + const client = new ExtendedClient({ intents: [] as ReadonlyArray<GatewayIntentBits> }); + + client.emit('ready' as unknown as never); + expect(client.notifier.subscribe).toHaveBeenCalled(); + }); }); }); }); diff --git a/src/base/client/ExtendedClient.ts b/src/base/client/ExtendedClient.ts index 809e3a7..e4dcae7 100644 --- a/src/base/client/ExtendedClient.ts +++ b/src/base/client/ExtendedClient.ts @@ -2,6 +2,7 @@ import { Client, ClientOptions, ClientEvents, ChatInputCommandInteraction } from import { CommandRegistry } from '../command/CommandRegistry'; import { InteractionDispatcher } from '../command/InteractionDispatcher'; import { Command } from '../command/Command'; +import { OffersNotifier } from '../../features/gameOffers/classes/OffersNotifier'; export interface ExtendedClientEvents extends ClientEvents { commandExecute: [command: Command, interaction: ChatInputCommandInteraction] @@ -38,17 +39,20 @@ export declare interface ExtendedClient { export class ExtendedClient extends Client { public readonly registry: CommandRegistry; public readonly dispatcher: InteractionDispatcher; + public readonly notifier: OffersNotifier; public constructor(options: ClientOptions) { super(options); this.registry = new CommandRegistry(this); this.dispatcher = new InteractionDispatcher(this, this.registry); + this.notifier = new OffersNotifier(this); this.registerBasicHandlers(); } private registerBasicHandlers(): void { this.on('interactionCreate', (interaction) => this.dispatcher.handleInteraction(interaction)); + this.on('ready', async () => await this.notifier.subscribe()); } } diff --git a/src/features/gameOffers/classes/OffersNotifier.spec.ts b/src/features/gameOffers/classes/OffersNotifier.spec.ts new file mode 100644 index 0000000..ab1c496 --- /dev/null +++ b/src/features/gameOffers/classes/OffersNotifier.spec.ts @@ -0,0 +1,81 @@ +import { OffersNotifier } from './OffersNotifier'; +import { ExtendedClient } from '../../../base/client/ExtendedClient'; +import { GameOffer } from '../../../models/gameOffer'; +import logger from '@moonstar-x/logger'; + +const triggerRef: { trigger: (() => void) | null } = { + trigger: null +}; + +jest.mock('../pubsub/subscribeToOffers', () => { + const gameOffer: GameOffer = { + storefront: 'Steam', + id: '442070', + url: 'https://store.steampowered.com/app/442070/Drawful_2/?snr=1_7_7_2300_150_1', + title: 'Drawful 2', + description: 'For 3-8 players and an audience of thousands! Your phones or tablets are your controllers! The game of terrible drawings and hilariously wrong answers.', + type: 'game', + publisher: 'Jackbox Games', + original_price: 5.79, + original_price_fmt: '$5.79 USD', + thumbnail: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/442070/header.jpg?t=1721927113' + }; + + return { + subscribeToOffers: jest.fn().mockImplementation((fn) => { + triggerRef.trigger = () => fn(gameOffer); + return jest.fn().mockImplementation(() => Promise.resolve()); + }) + }; +}); + +jest.mock('@moonstar-x/logger'); + +describe('Features > GameOffers > Classes > OffersNotifier', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('class OffersNotifier', () => { + const client = {} as ExtendedClient; + + it('should be defined.', () => { + expect(OffersNotifier).toBeDefined(); + }); + + describe('subscribe()', () => { + let notifier: OffersNotifier; + + beforeEach(() => { + notifier = new OffersNotifier(client); + }); + + it('should set subscription.', async () => { + expect((notifier as unknown as { subscription: unknown }).subscription).toBeNull(); + await notifier.subscribe(); + expect((notifier as unknown as { subscription: unknown }).subscription).not.toBeNull(); + }); + + it('should call unsubscribe if called repeatedly.', async () => { + await notifier.subscribe(); + const { subscription } = (notifier as unknown as { subscription: unknown }); + await notifier.subscribe(); + + expect(subscription).toHaveBeenCalled(); + }); + }); + + describe('subscription [handleOffer()]', () => { + const notifier = new OffersNotifier(client); + + beforeAll(async () => { + await notifier.subscribe(); + }); + + it('should log new offer.', () => { + triggerRef.trigger!(); + expect(logger.info).toHaveBeenCalledWith('Received new offer from Redis: Drawful 2 on Steam'); + }); + }); + }); +}); diff --git a/src/features/gameOffers/classes/OffersNotifier.ts b/src/features/gameOffers/classes/OffersNotifier.ts new file mode 100644 index 0000000..39e1ec7 --- /dev/null +++ b/src/features/gameOffers/classes/OffersNotifier.ts @@ -0,0 +1,27 @@ +import logger from '@moonstar-x/logger'; +import { ExtendedClient } from '../../../base/client/ExtendedClient'; +import { GameOffer } from '../../../models/gameOffer'; +import { subscribeToOffers, UnsubscribeFunction } from '../pubsub/subscribeToOffers'; + +export class OffersNotifier { + public readonly client: ExtendedClient; + + private subscription: UnsubscribeFunction | null; + + public constructor(client: ExtendedClient) { + this.client = client; + this.subscription = null; + } + + public async subscribe(): Promise<void> { + if (this.subscription) { + await this.subscription(); + } + + this.subscription = await subscribeToOffers(this.handleOffer.bind(this)); + } + + private async handleOffer(offer: GameOffer): Promise<void> { + logger.info(`Received new offer from Redis: ${offer.title} on ${offer.storefront}`); + } +} From 2a5341ce9ba15e81b7f59a863de1d6dc8e3aa2ba Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Thu, 8 Aug 2024 16:39:15 -0500 Subject: [PATCH 36/52] Fixed SQL function for get_notifiable_guilds(). --- migrations/001-init.sql | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/migrations/001-init.sql b/migrations/001-init.sql index 06f8f95..72909b4 100644 --- a/migrations/001-init.sql +++ b/migrations/001-init.sql @@ -148,9 +148,11 @@ BEGIN ) ), '[]'::json) FROM GuildSettings GS - INNER JOIN GuildGameOffersEnabled GGOE ON GS.guild = GGOE.guild - INNER JOIN GameOfferStorefront GOS ON GGOE.storefront_id = GOS.id - WHERE GS.channel IS NOT NULL AND GOS.name = storefront AND GGOE.enabled + WHERE GS.channel IS NOT NULL AND NOT EXISTS ( + SELECT 1 FROM GuildGameOffersEnabled GGOE + JOIN GameOfferStorefront GOS ON GGOE.storefront_id = GOS.id + WHERE GGOE.guild = GS.guild AND GOS.name = storefront AND GGOE.enabled = FALSE + ) ); END; $$ LANGUAGE plpgsql; From bb8b5de1df780bfe4893fbed02c24f1f21e545cd Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 10:11:04 -0500 Subject: [PATCH 37/52] Fixed client creation registering commands before event registration. --- src/app/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/client.ts b/src/app/client.ts index 3e322b0..4c246a5 100644 --- a/src/app/client.ts +++ b/src/app/client.ts @@ -9,8 +9,6 @@ export const createClient = () => { intents: [GatewayIntentBits.Guilds] as const }); - client.registry.registerIn(path.join(__dirname, '../commands')); - if (DEBUG_ENABLED) { client.on('debug', ClientEventHandlers.handleDebug); } @@ -25,5 +23,7 @@ export const createClient = () => { client.on('commandError', ClientEventHandlers.handleCommandError); client.on('commandRegistered', ClientEventHandlers.handleCommandRegistered); + client.registry.registerIn(path.join(__dirname, '../commands')); + return client; }; From 42a4395290b6d6c9a4ce8986929be71ab249ac21 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 10:12:20 -0500 Subject: [PATCH 38/52] Created a startSharded entrypoint. --- src/entrypoint/startShardedDev.spec.ts | 57 ++++++++++++++++++++++++++ src/entrypoint/startShardedDev.ts | 19 +++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/entrypoint/startShardedDev.spec.ts create mode 100644 src/entrypoint/startShardedDev.ts diff --git a/src/entrypoint/startShardedDev.spec.ts b/src/entrypoint/startShardedDev.spec.ts new file mode 100644 index 0000000..7d22491 --- /dev/null +++ b/src/entrypoint/startShardedDev.spec.ts @@ -0,0 +1,57 @@ +import { ShardingManager } from 'discord.js'; +import EventEmitter from 'events'; +import logger from '@moonstar-x/logger'; +import { runMigrations } from '../app/migration'; + +const manager = new EventEmitter() as unknown as ShardingManager; +manager.spawn = jest.fn(); + +jest.mock('../config/app', () => { + return { + DISCORD_TOKEN: 'token' + }; +}); + +jest.mock('../app/migration', () => { + return { + runMigrations: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + +jest.mock('discord.js', () => { + return { + ShardingManager: jest.fn().mockReturnValue(manager) + }; +}); + +jest.mock('@moonstar-x/logger'); + +describe('Entrypoint > Start Sharded (Dev)', () => { + const load = async () => { + jest.isolateModules(() => { + require('./startShardedDev'); + }); + await Promise.resolve(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call migrations.', async () => { + await load(); + expect(runMigrations).toHaveBeenCalled(); + }); + + it('should spawn manager.', async () => { + await load(); + expect(manager.spawn).toHaveBeenCalledWith({ amount: 2 }); + }); + + it('should register manager.shardCreate event handler.', async () => { + await load(); + (manager as unknown as EventEmitter).emit('shardCreate', { id: '123' }); + + expect(logger.info).toHaveBeenCalledWith('Launched shard with ID: 123.'); + }); +}); diff --git a/src/entrypoint/startShardedDev.ts b/src/entrypoint/startShardedDev.ts new file mode 100644 index 0000000..37e10a4 --- /dev/null +++ b/src/entrypoint/startShardedDev.ts @@ -0,0 +1,19 @@ +import path from 'path'; +import { ShardingManager } from 'discord.js'; +import logger from '@moonstar-x/logger'; +import { DISCORD_TOKEN } from '../config/app'; +import { runMigrations } from '../app/migration'; + +// Note: This works only when built, not in TypeScript. Command should build this prior to running. +const startScript = path.join(__dirname, `./start.js`); + +const manager = new ShardingManager(startScript, { token: DISCORD_TOKEN }); + +manager.on('shardCreate', (shard) => { + logger.info(`Launched shard with ID: ${shard.id}.`); +}); + +runMigrations() + .then(() => { + manager.spawn({ amount: 2 }); + }); From f65facf71dc2dc94baf7a798e0f51c0b13499ebf Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 10:14:29 -0500 Subject: [PATCH 39/52] Added a dev:sharded script to run bot sharded for development purposes. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index f122419..41760c6 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "private": true, "scripts": { "dev": "cross-env NODE_ENV=development nodemon src/entrypoint/start.ts", + "dev:sharded": "cross-env NODE_ENV=development npm run build && cross-env NODE_ENV=development node build/entrypoint/startShardedDev.js", "build": "tsc -p ./tsconfig.build.json", "type-check": "tsc --noEmit", "lint": "eslint . --ext .tsx,.ts,.js", From 3e2c00e5519fb0f485957d4304b90bc5f74dde2e Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 10:15:25 -0500 Subject: [PATCH 40/52] Fixed InfoCommand throwing when set channel no longer exists. --- src/commands/InfoCommand.spec.ts | 2 +- src/commands/InfoCommand.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/commands/InfoCommand.spec.ts b/src/commands/InfoCommand.spec.ts index 1084cec..a6d6768 100644 --- a/src/commands/InfoCommand.spec.ts +++ b/src/commands/InfoCommand.spec.ts @@ -126,7 +126,7 @@ describe('Commands > InfoCommand', () => { }); it('should reply with the correct embed if channel set does not exist.', async () => { - (client.channels.fetch as jest.Mock).mockResolvedValueOnce(null); + (client.channels.fetch as jest.Mock).mockRejectedValueOnce(null); await command.run(interaction); const expectedEmbeds = [ diff --git a/src/commands/InfoCommand.ts b/src/commands/InfoCommand.ts index 634f51c..40f2c7d 100644 --- a/src/commands/InfoCommand.ts +++ b/src/commands/InfoCommand.ts @@ -1,6 +1,6 @@ import { Command } from '../base/command/Command'; import { ExtendedClient } from '../base/client/ExtendedClient'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { Channel, EmbedBuilder, SlashCommandBuilder } from 'discord.js'; import { getGuild } from '../features/gameOffers/functions/getGuild'; import { GuildChatInputCommandInteraction } from '../base/types/aliases'; import { AVAILABLE_LOCALES, DEFAULT_LOCALE, getInteractionTranslator, translateAll, translateDefault } from '../i18n/translate'; @@ -29,7 +29,7 @@ export default class InfoCommand extends Command { } const guild = await this.client.guilds.fetch(guildInfo.guild); - const channel = guildInfo.channel ? await this.client.channels.fetch(guildInfo.channel) : null; + const channel = await this.getChannel(guildInfo.channel); const localeKey = AVAILABLE_LOCALES[guildInfo.locale] ?? AVAILABLE_LOCALES[DEFAULT_LOCALE]; const createdAt = new Date(guildInfo.created_at).toLocaleString(interaction.locale); @@ -53,4 +53,16 @@ export default class InfoCommand extends Command { await interaction.reply({ embeds: [embed] }); } + + private async getChannel(channelId: string | null): Promise<Channel | null> { + try { + if (!channelId) { + return null; + } + + return await this.client.channels.fetch(channelId); + } catch (error) { + return null; + } + } } From ce65240559e2652a18caf0691567de4433a29cd7 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 10:48:47 -0500 Subject: [PATCH 41/52] Created OfferNotifier service. --- .../gameOffers/classes/OffersNotifier.spec.ts | 134 +++++++++++++++++- .../gameOffers/classes/OffersNotifier.ts | 64 ++++++++- 2 files changed, 191 insertions(+), 7 deletions(-) diff --git a/src/features/gameOffers/classes/OffersNotifier.spec.ts b/src/features/gameOffers/classes/OffersNotifier.spec.ts index ab1c496..9a5811e 100644 --- a/src/features/gameOffers/classes/OffersNotifier.spec.ts +++ b/src/features/gameOffers/classes/OffersNotifier.spec.ts @@ -1,9 +1,11 @@ import { OffersNotifier } from './OffersNotifier'; import { ExtendedClient } from '../../../base/client/ExtendedClient'; -import { GameOffer } from '../../../models/gameOffer'; +import { GameOffer, NotifiableGuild } from '../../../models/gameOffer'; import logger from '@moonstar-x/logger'; +import { ChannelType, Guild, TextChannel } from 'discord.js'; +import { deleteGuild } from '../functions/deleteGuild'; -const triggerRef: { trigger: (() => void) | null } = { +const triggerRef: { trigger: (() => Promise<void>) | null } = { trigger: null }; @@ -23,12 +25,30 @@ jest.mock('../pubsub/subscribeToOffers', () => { return { subscribeToOffers: jest.fn().mockImplementation((fn) => { - triggerRef.trigger = () => fn(gameOffer); + triggerRef.trigger = async () => await fn(gameOffer); return jest.fn().mockImplementation(() => Promise.resolve()); }) }; }); +jest.mock('../functions/getNotifiableGuilds', () => { + const notifiableGuild: NotifiableGuild = { + guild: '1267881983548063785', + channel: '1267881983548063786', + locale: 'en-US' + }; + + return { + getNotifiableGuilds: jest.fn().mockResolvedValue([notifiableGuild]) + }; +}); + +jest.mock('../functions/deleteGuild', () => { + return { + deleteGuild: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + jest.mock('@moonstar-x/logger'); describe('Features > GameOffers > Classes > OffersNotifier', () => { @@ -37,7 +57,27 @@ describe('Features > GameOffers > Classes > OffersNotifier', () => { }); describe('class OffersNotifier', () => { - const client = {} as ExtendedClient; + const channel = { + type: ChannelType.GuildText, + send: jest.fn().mockImplementation(() => Promise.resolve()) + } as unknown as TextChannel; + + const guild = { + name: 'Guild', + shardId: 0, + channels: { + fetch: jest.fn().mockResolvedValue(channel) + } + } as unknown as Guild; + + const client = { + guilds: { + fetch: jest.fn().mockResolvedValue(guild) + }, + shard: { + ids: [0] + } + } as unknown as ExtendedClient; it('should be defined.', () => { expect(OffersNotifier).toBeDefined(); @@ -72,10 +112,92 @@ describe('Features > GameOffers > Classes > OffersNotifier', () => { await notifier.subscribe(); }); - it('should log new offer.', () => { - triggerRef.trigger!(); + it('should log new offer.', async () => { + await triggerRef.trigger!(); expect(logger.info).toHaveBeenCalledWith('Received new offer from Redis: Drawful 2 on Steam'); }); + + it('should log warning if guild is not found.', async () => { + (client.guilds.fetch as jest.Mock).mockRejectedValueOnce(null); + await triggerRef.trigger!(); + + expect(logger.warn).toHaveBeenCalledWith('Guild with id 1267881983548063785 does not exist anymore. Maybe the bot is no longer in it? Will remove from the database.'); + }); + + it('should guild not found.', async () => { + (client.guilds.fetch as jest.Mock).mockRejectedValueOnce(null); + await triggerRef.trigger!(); + + expect(deleteGuild).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith('Deleted guild data for 1267881983548063785.'); + }); + + it('should log error if guild delete fails..', async () => { + const expectedError = new Error('Oops'); + (client.guilds.fetch as jest.Mock).mockRejectedValueOnce(null); + (deleteGuild as jest.Mock).mockRejectedValueOnce(expectedError); + + await triggerRef.trigger!(); + + expect(logger.error).toHaveBeenCalledWith('Could not delete guild data for 1267881983548063785.'); + expect(logger.error).toHaveBeenCalledWith(expectedError); + }); + + it('should send the message if channel is found and is GuildText.', async () => { + await triggerRef.trigger!(); + expect(channel.send).toHaveBeenCalledWith({ embeds: [expect.anything()], components: [expect.anything()] }); + }); + + it('should send the message if channel is found and is GuildAnnouncement.', async () => { + Object.defineProperty(channel, 'type', { value: ChannelType.GuildAnnouncement }); + + await triggerRef.trigger!(); + expect(channel.send).toHaveBeenCalledWith({ embeds: [expect.anything()], components: [expect.anything()] }); + + Object.defineProperty(channel, 'type', { value: ChannelType.GuildText }); + }); + + it('should not send the message if guild is not in the same shard.', async () => { + guild.shardId = 1; + await triggerRef.trigger!(); + + expect(channel.send).not.toHaveBeenCalled(); + + guild.shardId = 0; + }); + + it('should not send the message if channel is null.', async () => { + (guild.channels.fetch as jest.Mock).mockResolvedValueOnce(null); + await triggerRef.trigger!(); + + expect(channel.send).not.toHaveBeenCalled(); + }); + + it('should not send the message if channel fetching fails.', async () => { + (guild.channels.fetch as jest.Mock).mockRejectedValueOnce(null); + await triggerRef.trigger!(); + + expect(channel.send).not.toHaveBeenCalled(); + }); + + it('should not send the message if channel is GuildText.', async () => { + Object.defineProperty(channel, 'type', { value: ChannelType.GuildForum }); + await triggerRef.trigger!(); + + expect(channel.send).not.toHaveBeenCalled(); + + Object.defineProperty(channel, 'type', { value: ChannelType.GuildText }); + }); + + it('should log error if something fails in sending.', async () => { + const expectedError = new Error('Oops!'); + (channel.send as jest.Mock).mockRejectedValueOnce(expectedError); + + await triggerRef.trigger!(); + + expect(logger.error).toHaveBeenCalledWith('Could not notify guild Guild about offer Drawful 2 on Steam.'); + expect(logger.error).toHaveBeenCalledWith(expectedError); + }); }); }); }); diff --git a/src/features/gameOffers/classes/OffersNotifier.ts b/src/features/gameOffers/classes/OffersNotifier.ts index 39e1ec7..107c8e1 100644 --- a/src/features/gameOffers/classes/OffersNotifier.ts +++ b/src/features/gameOffers/classes/OffersNotifier.ts @@ -1,7 +1,11 @@ import logger from '@moonstar-x/logger'; import { ExtendedClient } from '../../../base/client/ExtendedClient'; -import { GameOffer } from '../../../models/gameOffer'; +import { GameOffer, NotifiableGuild, offerToMessage } from '../../../models/gameOffer'; import { subscribeToOffers, UnsubscribeFunction } from '../pubsub/subscribeToOffers'; +import { getNotifiableGuilds } from '../functions/getNotifiableGuilds'; +import { ChannelType, Guild } from 'discord.js'; +import { deleteGuild } from '../functions/deleteGuild'; +import { getTranslator } from '../../../i18n/translate'; export class OffersNotifier { public readonly client: ExtendedClient; @@ -23,5 +27,63 @@ export class OffersNotifier { private async handleOffer(offer: GameOffer): Promise<void> { logger.info(`Received new offer from Redis: ${offer.title} on ${offer.storefront}`); + + const notifiableGuilds = await getNotifiableGuilds(offer.storefront); + + await Promise.all(notifiableGuilds.map(async (notifiableGuild) => { + const guild = await this.fetchGuild(notifiableGuild.guild); + + if (!guild) { + return this.handleNoGuildFound(notifiableGuild.guild); + } + + if (this.isGuildInThisShard(guild)) { + return this.notifyGuild(notifiableGuild, guild, offer); + } + })); + } + + private async handleNoGuildFound(guildId: string): Promise<void> { + try { + logger.warn(`Guild with id ${guildId} does not exist anymore. Maybe the bot is no longer in it? Will remove from the database.`); + await deleteGuild(guildId); + logger.info(`Deleted guild data for ${guildId}.`); + } catch (error) { + logger.error(`Could not delete guild data for ${guildId}.`); + logger.error(error); + } + } + + private async notifyGuild(notifiableGuild: NotifiableGuild, guild: Guild, offer: GameOffer): Promise<void> { + try { + const channel = await guild.channels.fetch(notifiableGuild.channel); + const t = getTranslator(notifiableGuild.locale); + + if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) { + return; + } + + const { component, embed } = offerToMessage(offer, t); + await channel.send({ embeds: [embed], components: [component] }); + } catch (error) { + logger.error(`Could not notify guild ${guild.name} about offer ${offer.title} on ${offer.storefront}.`); + logger.error(error); + } + } + + private async fetchGuild(guildId: string): Promise<Guild | null> { + try { + return await this.client.guilds.fetch(guildId); + } catch (error) { + return null; + } + } + + private isGuildInThisShard(guild: Guild): boolean { + if (!this.client.shard) { + return true; + } + + return this.client.shard.ids.some((id) => id === guild.shardId); } } From 83702b8ba01e1a3c843b78c1723b40a4a7bb7e82 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 17:10:44 -0500 Subject: [PATCH 42/52] Created a presence resolver to get preset presences. --- package-lock.json | 19 +++ package.json | 3 + src/base/presence/PresenceResolver.spec.ts | 142 +++++++++++++++++++++ src/base/presence/PresenceResolver.ts | 112 ++++++++++++++++ src/utils/array.spec.ts | 16 +++ src/utils/array.ts | 4 + 6 files changed, 296 insertions(+) create mode 100644 src/base/presence/PresenceResolver.spec.ts create mode 100644 src/base/presence/PresenceResolver.ts create mode 100644 src/utils/array.spec.ts create mode 100644 src/utils/array.ts diff --git a/package-lock.json b/package-lock.json index 4bcf7cb..dd06bfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,12 @@ "license": "MIT", "dependencies": { "@moonstar-x/logger": "^1.0.1", + "dayjs": "^1.11.12", "discord-api-types": "^0.37.93", "discord.js": "^14.15.3", "dotenv": "^16.4.5", "flat": "^5.0.2", + "humanize-duration": "^3.32.1", "intl-messageformat": "^10.5.14", "pg-promise": "^11.9.1", "redis": "^4.7.0", @@ -22,6 +24,7 @@ "devDependencies": { "@moonstar-x/eslint-config": "^1.0.0", "@types/flat": "^5.0.5", + "@types/humanize-duration": "^3.27.4", "@types/jest": "^29.5.12", "@types/node": "^22.0.0", "@types/require-all": "^3.0.6", @@ -3199,6 +3202,12 @@ "@types/node": "*" } }, + "node_modules/@types/humanize-duration": { + "version": "3.27.4", + "resolved": "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.4.tgz", + "integrity": "sha512-yaf7kan2Sq0goxpbcwTQ+8E9RP6HutFBPv74T/IA/ojcHKhuKVlk2YFYyHhWZeLvZPzzLE3aatuQB4h0iqyyUA==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4682,6 +4691,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", + "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==" + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -6496,6 +6510,11 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-duration": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.32.1.tgz", + "integrity": "sha512-inh5wue5XdfObhu/IGEMiA1nUXigSGcaKNemcbLRKa7jXYGDZXr3LoT9pTIzq2hPEbld7w/qv9h+ikWGz8fL1g==" + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", diff --git a/package.json b/package.json index 41760c6..3c51502 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,12 @@ }, "dependencies": { "@moonstar-x/logger": "^1.0.1", + "dayjs": "^1.11.12", "discord-api-types": "^0.37.93", "discord.js": "^14.15.3", "dotenv": "^16.4.5", "flat": "^5.0.2", + "humanize-duration": "^3.32.1", "intl-messageformat": "^10.5.14", "pg-promise": "^11.9.1", "redis": "^4.7.0", @@ -44,6 +46,7 @@ "devDependencies": { "@moonstar-x/eslint-config": "^1.0.0", "@types/flat": "^5.0.5", + "@types/humanize-duration": "^3.27.4", "@types/jest": "^29.5.12", "@types/node": "^22.0.0", "@types/require-all": "^3.0.6", diff --git a/src/base/presence/PresenceResolver.spec.ts b/src/base/presence/PresenceResolver.spec.ts new file mode 100644 index 0000000..733935e --- /dev/null +++ b/src/base/presence/PresenceResolver.spec.ts @@ -0,0 +1,142 @@ +import { PresenceResolver } from './PresenceResolver'; +import { ExtendedClient } from '../client/ExtendedClient'; +import { ShardClientUtil } from 'discord.js'; +import dayjs from 'dayjs'; + +dayjs.tz.setDefault('America/Guayaquil'); +const dateGetTimeSpy = jest.spyOn(Date.prototype, 'getTime', undefined as never); + +describe('Base > Presence > PresenceResolver', () => { + beforeAll(() => { + (dateGetTimeSpy as jest.Mock).mockReturnValue(1723224679000); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + (dateGetTimeSpy as jest.Mock).mockRestore(); + }); + + describe('class PresenceResolver', () => { + const clientShard = { + fetchClientValues: jest.fn() + } as unknown as ShardClientUtil; + const client = { + shard: null, + guilds: { + cache: { + reduce: jest.fn().mockImplementation((fn, init) => fn(init, { memberCount: 50 })), + size: 10 + } + }, + registry: { + size: jest.fn().mockReturnValue(5) + }, + readyTimestamp: null, + uptime: false + } as unknown as ExtendedClient; + const resolver = new PresenceResolver(client); + + it('should be defined.', () => { + expect(PresenceResolver).toBeDefined(); + }); + + describe('get()', () => { + describe('n_guilds', () => { + it('should return the number of guilds in a simple client.', async () => { + client.shard = null; + const result = await resolver.get('n_guilds'); + + expect(result).toBe('in 10 servers!'); + }); + + it('should return the number of guilds in a sharded client.', async () => { + (clientShard.fetchClientValues as jest.Mock).mockResolvedValueOnce([5, 10, 15]); + client.shard = clientShard; + const result = await resolver.get('n_guilds'); + + expect(result).toBe('in 30 servers!'); + }); + }); + + describe('n_members', () => { + it('should return the number of members in all guilds in a simple client.', async () => { + client.shard = null; + const result = await resolver.get('n_members'); + + expect(result).toBe('with 50 users!'); + }); + + it('should return the number of members in all guilds in a sharded client.', async () => { + (clientShard.fetchClientValues as jest.Mock).mockResolvedValueOnce([[{ memberCount: 10 }, { memberCount: 10 }], [{ memberCount: 50 }]]); + client.shard = clientShard; + const result = await resolver.get('n_members'); + + expect(result).toBe('with 70 users!'); + }); + }); + + describe('n_commands', () => { + it('should return the number of commands.', async () => { + const result = await resolver.get('n_commands'); + expect(result).toBe('with 5 commands!'); + }); + }); + + describe('n_commands', () => { + it('should return the number of commands.', async () => { + const result = await resolver.get('n_commands'); + expect(result).toBe('with 5 commands!'); + }); + }); + + describe('time_cur', () => { + it('should return the formatted current time.', async () => { + const result = await resolver.get('time_cur'); + expect(result).toBe('Current time: 12:31:19 PM'); + }); + }); + + describe('time_ready', () => { + it('should return the formatted ready time if exists.', async () => { + client.readyTimestamp = 1723224699000; + const result = await resolver.get('time_ready'); + expect(result).toBe('Up since: Fri, 09/08/24 @12:31:39 PM'); + }); + + it('should return the formatted now time if ready time does not exist.', async () => { + client.readyTimestamp = null; + const result = await resolver.get('time_ready'); + expect(result).toBe('Up since: Fri, 09/08/24 @12:31:19 PM'); + }); + }); + + describe('uptime', () => { + it('should return the formatted uptime if exists.', async () => { + Object.defineProperty(client, 'uptime', { value: 333000 }); + const result = await resolver.get('uptime'); + expect(result).toBe('Up for: 6 minutes'); + }); + + it('should return 0 minutes if uptime does not exist.', async () => { + Object.defineProperty(client, 'uptime', { value: false }); + const result = await resolver.get('uptime'); + expect(result).toBe('Up for: 0 minutes'); + }); + }); + + it('should throw if invalid name is provided.', () => { + expect(() => resolver.get('whatever' as 'n_guilds')).rejects.toThrow(new Error('Invalid presence name provided.')); + }); + }); + + describe('getRandom()', () => { + it('should return a random text.', async () => { + const result = await resolver.getRandom(); + expect(typeof result).toBe('string'); + }); + }); + }); +}); diff --git a/src/base/presence/PresenceResolver.ts b/src/base/presence/PresenceResolver.ts new file mode 100644 index 0000000..88f0d75 --- /dev/null +++ b/src/base/presence/PresenceResolver.ts @@ -0,0 +1,112 @@ +import { ExtendedClient } from '../client/ExtendedClient'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import humanizeDuration from 'humanize-duration'; +import { Collection, Guild, Snowflake } from 'discord.js'; +import { randomItem } from '../../utils/array'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +const PRESENCE_NAMES = ['n_guilds', 'n_members', 'n_commands', 'time_cur', 'time_ready', 'uptime'] as const; +type PresenceName = typeof PRESENCE_NAMES[number]; + +export class PresenceResolver { + public readonly client: ExtendedClient; + + public constructor(client: ExtendedClient) { + this.client = client; + } + + public async get(name: PresenceName): Promise<string> { + const value = await this.getValue(name); + + switch (name) { + case 'n_guilds': + return `in ${value} servers!`; + case 'n_members': + return `with ${value} users!`; + case 'n_commands': + return `with ${value} commands!`; + case 'time_cur': + return `Current time: ${value}`; + case 'time_ready': + return `Up since: ${value}`; + case 'uptime': + return `Up for: ${value}`; + default: + throw new Error('Invalid presence name provided.'); + } + } + + public getRandom(): Promise<string> { + return this.get(randomItem(PRESENCE_NAMES)); + } + + private getValue(name: PresenceName): Promise<string> { + switch (name) { + case 'n_guilds': + return this.getNumberOfGuilds(); + case 'n_members': + return this.getNumberOfMembers(); + case 'n_commands': + return this.getNumberOfCommands(); + case 'time_cur': + return this.getCurrentTime(); + case 'time_ready': + return this.getReadyTime(); + case 'uptime': + return this.getUptime(); + default: + throw new Error('Invalid presence name provided.'); + } + } + + private async getNumberOfGuilds(): Promise<string> { + if (!this.client.shard) { + return this.client.guilds.cache.size.toString(); + } + + const results = await this.client.shard.fetchClientValues('guilds.cache.size'); + return (results as number[]).reduce((sum, size) => sum + size, 0).toString(); + } + + private async getNumberOfMembers(): Promise<string> { + if (!this.client.shard) { + return this.client.guilds.cache.reduce((sum, guild) => sum + guild.memberCount, 0).toString(); + } + + const results = await this.client.shard.fetchClientValues('guilds.cache'); + return (results as Collection<Snowflake, Guild>[]).reduce((sum, cache) => { + const members = cache.reduce((sum: number, guild: Guild) => sum + guild.memberCount, 0); + return sum + members; + }, 0).toString(); + } + + private async getNumberOfCommands(): Promise<string> { + return this.client.registry.size().toString(); + } + + private async getCurrentTime(): Promise<string> { + const now = new Date().getTime(); + return dayjs(now).tz().format('hh:mm:ss A'); + } + + private async getReadyTime(): Promise<string> { + const readyTimestamp = this.client.readyTimestamp ?? new Date().getTime(); + return dayjs(readyTimestamp).tz().format('ddd, DD/MM/YY @hh:mm:ss A'); + } + + private async getUptime(): Promise<string> { + const uptime = this.client.uptime || 0; + + return humanizeDuration(uptime, { + largest: 3, + units: ['d', 'h', 'm'], + round: true, + conjunction: ' and ', + serialComma: false + }); + } +} diff --git a/src/utils/array.spec.ts b/src/utils/array.spec.ts new file mode 100644 index 0000000..5869305 --- /dev/null +++ b/src/utils/array.spec.ts @@ -0,0 +1,16 @@ +import { randomItem } from './array'; + +describe('Utils > Array', () => { + describe('randomItem()', () => { + it('should be defined.', () => { + expect(randomItem).toBeDefined(); + }); + + it('should return an item from the array.', () => { + const arr = [1, 2, 3, 4, 5, 6]; + const result = randomItem(arr); + + expect(arr).toContain(result); + }); + }); +}); diff --git a/src/utils/array.ts b/src/utils/array.ts new file mode 100644 index 0000000..7d2d2b1 --- /dev/null +++ b/src/utils/array.ts @@ -0,0 +1,4 @@ +export const randomItem = <T>(arr: Array<T> | ReadonlyArray<T>): T => { + const randomIndex = Math.floor(Math.random() * arr.length); + return arr[randomIndex]; +}; From 6d5f50a6d51efa33aae1ddcae11ae78306d18d71 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 21:04:45 -0500 Subject: [PATCH 43/52] Implemented PresenceManager. --- .eslintrc | 3 + src/base/presence/PresenceManager.spec.ts | 164 ++++++++++++++++++++++ src/base/presence/PresenceManager.ts | 78 ++++++++++ 3 files changed, 245 insertions(+) create mode 100644 src/base/presence/PresenceManager.spec.ts create mode 100644 src/base/presence/PresenceManager.ts diff --git a/.eslintrc b/.eslintrc index d6d56ed..e9c67bc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,7 @@ { + "globals": { + "NodeJS": true + }, "extends": [ "@moonstar-x/eslint-config/rules/node", "@moonstar-x/eslint-config/rules/typescript" diff --git a/src/base/presence/PresenceManager.spec.ts b/src/base/presence/PresenceManager.spec.ts new file mode 100644 index 0000000..06fee13 --- /dev/null +++ b/src/base/presence/PresenceManager.spec.ts @@ -0,0 +1,164 @@ +import { PresenceManager } from './PresenceManager'; +import { ExtendedClient } from '../client/ExtendedClient'; +import { PresenceResolver } from './PresenceResolver'; +import { ActivityType } from 'discord.js'; +import logger from '@moonstar-x/logger'; + +jest.mock('@moonstar-x/logger'); + +describe('Base > Presence > PresenceManager', () => { + describe('class PresenceManager', () => { + const client = { + user: { + setPresence: jest.fn() + } + } as unknown as ExtendedClient; + const resolver = { + getRandom: jest.fn().mockResolvedValue('presence') + } as unknown as PresenceResolver; + + beforeAll(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + jest.clearAllTimers(); + }); + + it('should be defined.', () => { + expect(PresenceManager).toBeDefined(); + }); + + describe('constructor', () => { + it('should set the provided options.', () => { + const manager = new PresenceManager(client, resolver, { + status: 'idle', + type: ActivityType.Competing, + afk: true + }); + + expect(manager.options.status).toBe('idle'); + expect(manager.options.type).toBe(ActivityType.Competing); + expect(manager.options.afk).toBe(true); + }); + + it('should set the default options.', () => { + const manager = new PresenceManager(client, resolver); + + expect(manager.options.status).toBe('online'); + expect(manager.options.type).toBe(ActivityType.Playing); + expect(manager.options.afk).toBe(false); + }); + }); + + describe('setRefreshInterval()', () => { + let manager: PresenceManager; + const clearIntervalSpy = jest.spyOn(global, 'clearInterval', undefined as never); + + beforeEach(async () => { + if (manager) { + await manager.setRefreshInterval(null); + } + manager = new PresenceManager(client, resolver); + }); + + afterAll(async () => { + await manager.setRefreshInterval(null); + (clearIntervalSpy as jest.Mock).mockRestore(); + }); + + it('should throw if provided interval is inferior to minimum.', () => { + expect(() => { + return manager.setRefreshInterval(999); + }).rejects.toThrow(new Error('Interval should be greater than 1000.')); + }); + + it('should set the interval handle.', async () => { + await manager.setRefreshInterval(1001); + expect((manager as unknown as { intervalHandle: number }).intervalHandle).not.toBeNull(); + }); + + it('should clear the interval if already set and remove it if set to null.', async () => { + await manager.setRefreshInterval(5000); + const oldHandle = (manager as unknown as { intervalHandle: number }).intervalHandle; + + await manager.setRefreshInterval(null); + expect(clearIntervalSpy).toHaveBeenCalledWith(oldHandle); + expect((manager as unknown as { intervalHandle: number }).intervalHandle).toBeNull(); + }); + + it('should clear the interval if already set and update the interval handle.', async () => { + await manager.setRefreshInterval(5000); + const oldHandle = (manager as unknown as { intervalHandle: number }).intervalHandle; + + await manager.setRefreshInterval(6000); + expect(clearIntervalSpy).toHaveBeenCalledWith(oldHandle); + expect((manager as unknown as { intervalHandle: number }).intervalHandle).not.toBe(oldHandle); + }); + }); + + describe('setPresence()', () => { + const manager = new PresenceManager(client, resolver); + + it('should set presence with correct values.', () => { + manager.setPresence('something'); + expect(client.user!.setPresence).toHaveBeenCalledWith({ + activities: [{ + name: 'something', + type: ActivityType.Playing + }], + status: 'online', + afk: false + }); + }); + + it('should log presence change.', () => { + manager.setPresence('something'); + expect(logger.info).toHaveBeenCalledWith('Presence changed to: something'); + }); + + it('should log error if presence change fails.', () => { + const expectedError = new Error('Oops'); + (client.user!.setPresence as jest.Mock).mockImplementationOnce(() => { + throw expectedError; + }); + + manager.setPresence('something'); + expect(logger.error).toHaveBeenCalledWith('Could not update client presence.'); + expect(logger.error).toHaveBeenCalledWith(expectedError); + }); + }); + + describe('update()', () => { + const manager = new PresenceManager(client, resolver); + + it('should set presence with correct values.', async () => { + await manager.update(); + expect(client.user!.setPresence).toHaveBeenCalledWith({ + activities: [{ + name: 'presence', + type: ActivityType.Playing + }], + status: 'online', + afk: false + }); + }); + + it('should log presence change.', async () => { + await manager.update(); + expect(logger.info).toHaveBeenCalledWith('Presence changed to: presence'); + }); + + it('should log error if presence change fails.', async () => { + const expectedError = new Error('Oops'); + (client.user!.setPresence as jest.Mock).mockImplementationOnce(() => { + throw expectedError; + }); + + await manager.update(); + expect(logger.error).toHaveBeenCalledWith('Could not update client presence.'); + expect(logger.error).toHaveBeenCalledWith(expectedError); + }); + }); + }); +}); diff --git a/src/base/presence/PresenceManager.ts b/src/base/presence/PresenceManager.ts new file mode 100644 index 0000000..3af92ec --- /dev/null +++ b/src/base/presence/PresenceManager.ts @@ -0,0 +1,78 @@ +import { PresenceResolver } from './PresenceResolver'; +import { ExtendedClient } from '../client/ExtendedClient'; +import { ActivityType, PresenceStatusData } from 'discord.js'; +import logger from '@moonstar-x/logger'; + +const MIN_INTERVAL_MS = 1000; + +export interface PresenceManagerOptions { + status?: PresenceStatusData + type?: ActivityType + afk?: boolean +} + +export class PresenceManager { + public readonly client: ExtendedClient; + public readonly resolver: PresenceResolver; + public readonly options: Required<PresenceManagerOptions>; + + private intervalHandle: NodeJS.Timeout | null; + + public constructor(client: ExtendedClient, resolver: PresenceResolver, options: PresenceManagerOptions = {}) { + this.client = client; + this.resolver = resolver; + + this.options = { + status: options.status ?? 'online', + type: options.type ?? ActivityType.Playing, + afk: options.afk ?? false + }; + this.intervalHandle = null; + } + + public async setRefreshInterval(intervalMs: number | null): Promise<void> { + if (!intervalMs) { + this.clearInterval(); + return; + } + + if (intervalMs < MIN_INTERVAL_MS) { + throw new Error(`Interval should be greater than ${MIN_INTERVAL_MS}.`); + } + + this.clearInterval(); + await this.update(); + this.intervalHandle = setInterval(() => this.update(), intervalMs); + } + + public setPresence(presence: string): void { + try { + this.client.user?.setPresence({ + activities: [{ + name: presence, + type: this.options.type + }], + status: this.options.status, + afk: this.options.afk + }); + + logger.info(`Presence changed to: ${presence}`); + } catch (error) { + logger.error('Could not update client presence.'); + logger.error(error); + } + } + + public async update(): Promise<void> { + const presence = await this.resolver.getRandom(); + this.setPresence(presence); + } + + private clearInterval() { + if (this.intervalHandle) { + clearInterval(this.intervalHandle); + } + + this.intervalHandle = null; + } +} From b8d73e1785c38c37ad591a910f2676915d02c5ed Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 21:16:07 -0500 Subject: [PATCH 44/52] Added PresenceManager to ExtendedClient, --- src/base/client/ExtendedClient.spec.ts | 25 +++++++++++++++++++++- src/base/client/ExtendedClient.ts | 9 +++++++- src/base/presence/PresenceResolver.spec.ts | 4 ++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/base/client/ExtendedClient.spec.ts b/src/base/client/ExtendedClient.spec.ts index 822a24f..3e17f44 100644 --- a/src/base/client/ExtendedClient.spec.ts +++ b/src/base/client/ExtendedClient.spec.ts @@ -21,6 +21,26 @@ jest.mock('../../features/gameOffers/classes/OffersNotifier', () => { }; }); +jest.mock('../presence/PresenceManager', () => { + return { + PresenceManager: jest.fn().mockImplementation(() => { + return { + setRefreshInterval: jest.fn() + }; + }) + }; +}); + +jest.mock('../presence/PresenceResolver', () => { + return { + PresenceResolver: jest.fn().mockImplementation(() => { + return { + getRandom: jest.fn().mockReturnValue('presence') + }; + }) + }; +}); + describe('Base > Client > ExtendedClient', () => { beforeEach(() => { jest.clearAllMocks(); @@ -40,11 +60,14 @@ describe('Base > Client > ExtendedClient', () => { expect(client.dispatcher.handleInteraction).toHaveBeenCalledWith(interaction); }); - it('should register ready on construction.', () => { + it('should register ready on construction.', async () => { const client = new ExtendedClient({ intents: [] as ReadonlyArray<GatewayIntentBits> }); client.emit('ready' as unknown as never); + await Promise.resolve(); // Helps waiting for next tick. + expect(client.notifier.subscribe).toHaveBeenCalled(); + expect(client.presenceManager.setRefreshInterval).toHaveBeenCalled(); }); }); }); diff --git a/src/base/client/ExtendedClient.ts b/src/base/client/ExtendedClient.ts index e4dcae7..74c7cc0 100644 --- a/src/base/client/ExtendedClient.ts +++ b/src/base/client/ExtendedClient.ts @@ -3,6 +3,8 @@ import { CommandRegistry } from '../command/CommandRegistry'; import { InteractionDispatcher } from '../command/InteractionDispatcher'; import { Command } from '../command/Command'; import { OffersNotifier } from '../../features/gameOffers/classes/OffersNotifier'; +import { PresenceManager } from '../presence/PresenceManager'; +import { PresenceResolver } from '../presence/PresenceResolver'; export interface ExtendedClientEvents extends ClientEvents { commandExecute: [command: Command, interaction: ChatInputCommandInteraction] @@ -40,6 +42,7 @@ export class ExtendedClient extends Client { public readonly registry: CommandRegistry; public readonly dispatcher: InteractionDispatcher; public readonly notifier: OffersNotifier; + public readonly presenceManager: PresenceManager; public constructor(options: ClientOptions) { super(options); @@ -47,12 +50,16 @@ export class ExtendedClient extends Client { this.registry = new CommandRegistry(this); this.dispatcher = new InteractionDispatcher(this, this.registry); this.notifier = new OffersNotifier(this); + this.presenceManager = new PresenceManager(this, new PresenceResolver(this)); this.registerBasicHandlers(); } private registerBasicHandlers(): void { this.on('interactionCreate', (interaction) => this.dispatcher.handleInteraction(interaction)); - this.on('ready', async () => await this.notifier.subscribe()); + this.once('ready', async () => { + await this.notifier.subscribe(); + await this.presenceManager.setRefreshInterval(5 * 60 * 1000); + }); } } diff --git a/src/base/presence/PresenceResolver.spec.ts b/src/base/presence/PresenceResolver.spec.ts index 733935e..d014f66 100644 --- a/src/base/presence/PresenceResolver.spec.ts +++ b/src/base/presence/PresenceResolver.spec.ts @@ -133,6 +133,10 @@ describe('Base > Presence > PresenceResolver', () => { }); describe('getRandom()', () => { + beforeAll(() => { + client.shard = null; + }); + it('should return a random text.', async () => { const result = await resolver.getRandom(); expect(typeof result).toBe('string'); From 71137277dfdf33237cbf6a3ad26790cdc00fa64a Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 21:28:50 -0500 Subject: [PATCH 45/52] Added new env config. --- src/base/client/ExtendedClient.ts | 3 ++- src/config/app.spec.ts | 29 +++++++++++++++++++++++++++++ src/config/app.ts | 3 +++ src/global.d.ts | 3 +++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/base/client/ExtendedClient.ts b/src/base/client/ExtendedClient.ts index 74c7cc0..7bf040e 100644 --- a/src/base/client/ExtendedClient.ts +++ b/src/base/client/ExtendedClient.ts @@ -5,6 +5,7 @@ import { Command } from '../command/Command'; import { OffersNotifier } from '../../features/gameOffers/classes/OffersNotifier'; import { PresenceManager } from '../presence/PresenceManager'; import { PresenceResolver } from '../presence/PresenceResolver'; +import { DISCORD_PRESENCE_INTERVAL } from '../../config/app'; export interface ExtendedClientEvents extends ClientEvents { commandExecute: [command: Command, interaction: ChatInputCommandInteraction] @@ -59,7 +60,7 @@ export class ExtendedClient extends Client { this.on('interactionCreate', (interaction) => this.dispatcher.handleInteraction(interaction)); this.once('ready', async () => { await this.notifier.subscribe(); - await this.presenceManager.setRefreshInterval(5 * 60 * 1000); + await this.presenceManager.setRefreshInterval(DISCORD_PRESENCE_INTERVAL); }); } } diff --git a/src/config/app.spec.ts b/src/config/app.spec.ts index fde0787..ffa3016 100644 --- a/src/config/app.spec.ts +++ b/src/config/app.spec.ts @@ -6,6 +6,9 @@ describe('Config > App', () => { const oldEnv = { ...process.env }; const mockedEnv = { DISCORD_TOKEN: 'token', + DISCORD_PRESENCE_INTERVAL: '10000', + DISCORD_SHARDING_ENABLED: 'true', + DISCORD_SHARDING_COUNT: '5', REDIS_URI: 'redis://localhost:6379', POSTGRES_HOST: 'localhost', POSTGRES_PORT: '5431', @@ -34,6 +37,32 @@ describe('Config > App', () => { expect(config.DISCORD_TOKEN).toBe('token'); }); + it('should export valid DISCORD_PRESENCE_INTERVAL.', () => { + expect(config.DISCORD_PRESENCE_INTERVAL).toBe(10000); + }); + + it('should export valid DISCORD_PRESENCE_INTERVAL if no value is provided.', () => { + process.env = { ...mockedEnv, DISCORD_PRESENCE_INTERVAL: undefined as unknown as string }; + resetModule(); + + expect(config.DISCORD_PRESENCE_INTERVAL).toBe(5 * 60 * 1000); + }); + + it('should export valid DISCORD_SHARDING_ENABLED.', () => { + expect(config.DISCORD_SHARDING_ENABLED).toBe(true); + }); + + it('should export valid DISCORD_SHARDING_COUNT.', () => { + expect(config.DISCORD_SHARDING_COUNT).toBe(5); + }); + + it('should export valid DISCORD_SHARDING_COUNT if no value is provided.', () => { + process.env = { ...mockedEnv, DISCORD_SHARDING_COUNT: undefined as unknown as string }; + resetModule(); + + expect(config.DISCORD_SHARDING_COUNT).toBe('auto'); + }); + it('should export valid REDIS_URI.', () => { expect(config.REDIS_URI).toBe('redis://localhost:6379'); }); diff --git a/src/config/app.ts b/src/config/app.ts index 19a1a63..72a9890 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -1,6 +1,9 @@ import 'dotenv/config'; export const DISCORD_TOKEN = process.env.DISCORD_TOKEN!; +export const DISCORD_PRESENCE_INTERVAL = process.env.DISCORD_PRESENCE_INTERVAL ? parseInt(process.env.DISCORD_PRESENCE_INTERVAL, 10) : 5 * 60 * 1000; +export const DISCORD_SHARDING_ENABLED = process.env.DISCORD_SHARDING_ENABLED === 'true'; +export const DISCORD_SHARDING_COUNT: number | 'auto' = process.env.DISCORD_SHARDING_COUNT ? process.env.DISCORD_SHARDING_COUNT !== 'auto' ? parseInt(process.env.DISCORD_SHARDING_COUNT, 10) : 'auto' : 'auto'; export const REDIS_URI = process.env.REDIS_URI!; diff --git a/src/global.d.ts b/src/global.d.ts index 0d60fb8..7fccd5d 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -4,6 +4,9 @@ declare global { namespace NodeJS { interface ProcessEnv { DISCORD_TOKEN?: string + DISCORD_PRESENCE_INTERVAL?: string + DISCORD_SHARDING_ENABLED?: string + DISCORD_SHARDING_COUNT?: string REDIS_URI?: string From 2ea164164b6b59739f43d0baea9cf455f38c47c6 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Fri, 9 Aug 2024 22:05:13 -0500 Subject: [PATCH 46/52] Updated entrypoints to enable sharding based on env variable. --- package.json | 9 ++-- src/entrypoint/start.spec.ts | 48 +++++++++++----------- src/entrypoint/start.ts | 15 +++---- src/entrypoint/startSharded.spec.ts | 3 +- src/entrypoint/startSharded.ts | 6 +-- src/entrypoint/startShardedDev.spec.ts | 57 -------------------------- src/entrypoint/startShardedDev.ts | 19 --------- src/entrypoint/startSingle.spec.ts | 47 +++++++++++++++++++++ src/entrypoint/startSingle.ts | 10 +++++ 9 files changed, 96 insertions(+), 118 deletions(-) delete mode 100644 src/entrypoint/startShardedDev.spec.ts delete mode 100644 src/entrypoint/startShardedDev.ts create mode 100644 src/entrypoint/startSingle.spec.ts create mode 100644 src/entrypoint/startSingle.ts diff --git a/package.json b/package.json index 3c51502..348a4ba 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "A Discord bot that will notify you when games on various storefronts become free.", "private": true, "scripts": { - "dev": "cross-env NODE_ENV=development nodemon src/entrypoint/start.ts", - "dev:sharded": "cross-env NODE_ENV=development npm run build && cross-env NODE_ENV=development node build/entrypoint/startShardedDev.js", + "dev": "cross-env NODE_ENV=development nodemon src/entrypoint/startSingle.ts", + "dev:sharded": "cross-env NODE_ENV=development npm run build && cross-env NODE_ENV=development DISCORD_SHARDING_ENABLED=true DISCORD_SHARDING_COUNT=2 node build/entrypoint/start.js", "build": "tsc -p ./tsconfig.build.json", "type-check": "tsc --noEmit", "lint": "eslint . --ext .tsx,.ts,.js", @@ -13,9 +13,8 @@ "test": "jest", "test:watch": "jest --watch", "deploy:dev": "ts-node src/entrypoint/deploy.ts", - "deploy:prod": "node build/entrypoint/deploy.ts", - "start": "node build/entrypoint/start.js", - "start:sharded": "node build/entrypoint/startSharded.js" + "deploy:prod": "node build/entrypoint/deploy.js", + "start": "node build/entrypoint/start.js" }, "repository": { "type": "git", diff --git a/src/entrypoint/start.spec.ts b/src/entrypoint/start.spec.ts index 7fc5add..6684fb5 100644 --- a/src/entrypoint/start.spec.ts +++ b/src/entrypoint/start.spec.ts @@ -1,29 +1,18 @@ -import { ExtendedClient } from '../base/client/ExtendedClient'; -import { runMigrations } from '../app/migration'; +const requiredRef = { + required: (name: string) => name +}; -const client = { - login: jest.fn() -} as unknown as ExtendedClient; - -jest.mock('../app/client', () => { - return { - createClient: jest.fn().mockReturnValue(client) - }; +jest.mock('./startSingle', () => { + requiredRef.required('single'); }); -jest.mock('../app/migration', () => { - return { - runMigrations: jest.fn().mockImplementation(() => Promise.resolve()) - }; -}); - -jest.mock('../config/app', () => { - return { - DISCORD_TOKEN: 'token' - }; +jest.mock('./startSharded', () => { + requiredRef.required('sharded'); }); describe('Entrypoint > Start', () => { + const requiredMock = jest.spyOn(requiredRef, 'required', undefined as never); + const load = async () => { jest.isolateModules(() => { require('./start'); @@ -33,15 +22,26 @@ describe('Entrypoint > Start', () => { beforeEach(() => { jest.clearAllMocks(); + jest.resetModules(); }); - it('should call migrations.', async () => { + it('should require single if sharding is disabled.', async () => { + jest.mock('../config/app', () => { + return { + DISCORD_SHARDING_ENABLED: false + }; + }); await load(); - expect(runMigrations).toHaveBeenCalled(); + expect(requiredMock).toHaveBeenCalledWith('single'); }); - it('should login client with token.', async () => { + it('should require sharded if sharding is enabled.', async () => { + jest.mock('../config/app', () => { + return { + DISCORD_SHARDING_ENABLED: true + }; + }); await load(); - expect(client.login).toHaveBeenCalledWith('token'); + expect(requiredMock).toHaveBeenCalledWith('sharded'); }); }); diff --git a/src/entrypoint/start.ts b/src/entrypoint/start.ts index bed2da3..23d5aff 100644 --- a/src/entrypoint/start.ts +++ b/src/entrypoint/start.ts @@ -1,10 +1,7 @@ -import { createClient } from '../app/client'; -import { DISCORD_TOKEN } from '../config/app'; -import { runMigrations } from '../app/migration'; +import { DISCORD_SHARDING_ENABLED } from '../config/app'; -const client = createClient(); - -runMigrations() - .then(() => { - client.login(DISCORD_TOKEN); - }); +if (DISCORD_SHARDING_ENABLED) { + require('./startSharded'); +} else { + require('./startSingle'); +} diff --git a/src/entrypoint/startSharded.spec.ts b/src/entrypoint/startSharded.spec.ts index c2e576e..fb86f07 100644 --- a/src/entrypoint/startSharded.spec.ts +++ b/src/entrypoint/startSharded.spec.ts @@ -8,7 +8,8 @@ manager.spawn = jest.fn(); jest.mock('../config/app', () => { return { - DISCORD_TOKEN: 'token' + DISCORD_TOKEN: 'token', + DISCORD_SHARDING_COUNT: 'auto' }; }); diff --git a/src/entrypoint/startSharded.ts b/src/entrypoint/startSharded.ts index 47aad63..4bafed0 100644 --- a/src/entrypoint/startSharded.ts +++ b/src/entrypoint/startSharded.ts @@ -1,11 +1,11 @@ import path from 'path'; import { ShardingManager } from 'discord.js'; import logger from '@moonstar-x/logger'; -import { DISCORD_TOKEN } from '../config/app'; +import { DISCORD_SHARDING_COUNT, DISCORD_TOKEN } from '../config/app'; import { runMigrations } from '../app/migration'; // Note: This works only when built, not in TypeScript. -const startScript = path.join(__dirname, `./start.js`); +const startScript = path.join(__dirname, `./startSingle.js`); const manager = new ShardingManager(startScript, { token: DISCORD_TOKEN }); @@ -15,5 +15,5 @@ manager.on('shardCreate', (shard) => { runMigrations() .then(() => { - manager.spawn({ amount: 'auto' }); + manager.spawn({ amount: DISCORD_SHARDING_COUNT }); }); diff --git a/src/entrypoint/startShardedDev.spec.ts b/src/entrypoint/startShardedDev.spec.ts deleted file mode 100644 index 7d22491..0000000 --- a/src/entrypoint/startShardedDev.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ShardingManager } from 'discord.js'; -import EventEmitter from 'events'; -import logger from '@moonstar-x/logger'; -import { runMigrations } from '../app/migration'; - -const manager = new EventEmitter() as unknown as ShardingManager; -manager.spawn = jest.fn(); - -jest.mock('../config/app', () => { - return { - DISCORD_TOKEN: 'token' - }; -}); - -jest.mock('../app/migration', () => { - return { - runMigrations: jest.fn().mockImplementation(() => Promise.resolve()) - }; -}); - -jest.mock('discord.js', () => { - return { - ShardingManager: jest.fn().mockReturnValue(manager) - }; -}); - -jest.mock('@moonstar-x/logger'); - -describe('Entrypoint > Start Sharded (Dev)', () => { - const load = async () => { - jest.isolateModules(() => { - require('./startShardedDev'); - }); - await Promise.resolve(); - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should call migrations.', async () => { - await load(); - expect(runMigrations).toHaveBeenCalled(); - }); - - it('should spawn manager.', async () => { - await load(); - expect(manager.spawn).toHaveBeenCalledWith({ amount: 2 }); - }); - - it('should register manager.shardCreate event handler.', async () => { - await load(); - (manager as unknown as EventEmitter).emit('shardCreate', { id: '123' }); - - expect(logger.info).toHaveBeenCalledWith('Launched shard with ID: 123.'); - }); -}); diff --git a/src/entrypoint/startShardedDev.ts b/src/entrypoint/startShardedDev.ts deleted file mode 100644 index 37e10a4..0000000 --- a/src/entrypoint/startShardedDev.ts +++ /dev/null @@ -1,19 +0,0 @@ -import path from 'path'; -import { ShardingManager } from 'discord.js'; -import logger from '@moonstar-x/logger'; -import { DISCORD_TOKEN } from '../config/app'; -import { runMigrations } from '../app/migration'; - -// Note: This works only when built, not in TypeScript. Command should build this prior to running. -const startScript = path.join(__dirname, `./start.js`); - -const manager = new ShardingManager(startScript, { token: DISCORD_TOKEN }); - -manager.on('shardCreate', (shard) => { - logger.info(`Launched shard with ID: ${shard.id}.`); -}); - -runMigrations() - .then(() => { - manager.spawn({ amount: 2 }); - }); diff --git a/src/entrypoint/startSingle.spec.ts b/src/entrypoint/startSingle.spec.ts new file mode 100644 index 0000000..fbb9af9 --- /dev/null +++ b/src/entrypoint/startSingle.spec.ts @@ -0,0 +1,47 @@ +import { ExtendedClient } from '../base/client/ExtendedClient'; +import { runMigrations } from '../app/migration'; + +const client = { + login: jest.fn() +} as unknown as ExtendedClient; + +jest.mock('../app/client', () => { + return { + createClient: jest.fn().mockReturnValue(client) + }; +}); + +jest.mock('../app/migration', () => { + return { + runMigrations: jest.fn().mockImplementation(() => Promise.resolve()) + }; +}); + +jest.mock('../config/app', () => { + return { + DISCORD_TOKEN: 'token' + }; +}); + +describe('Entrypoint > Start Single', () => { + const load = async () => { + jest.isolateModules(() => { + require('./startSingle'); + }); + await Promise.resolve(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call migrations.', async () => { + await load(); + expect(runMigrations).toHaveBeenCalled(); + }); + + it('should login client with token.', async () => { + await load(); + expect(client.login).toHaveBeenCalledWith('token'); + }); +}); diff --git a/src/entrypoint/startSingle.ts b/src/entrypoint/startSingle.ts new file mode 100644 index 0000000..bed2da3 --- /dev/null +++ b/src/entrypoint/startSingle.ts @@ -0,0 +1,10 @@ +import { createClient } from '../app/client'; +import { DISCORD_TOKEN } from '../config/app'; +import { runMigrations } from '../app/migration'; + +const client = createClient(); + +runMigrations() + .then(() => { + client.login(DISCORD_TOKEN); + }); From 1b149f0ef273aad73291b96a08597a993c71cb62 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 12 Aug 2024 12:29:22 -0500 Subject: [PATCH 47/52] Added Docker image. --- .dockerignore | 11 +++++++++++ Dockerfile | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1c5c659 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +# Ignore everything by default. +* + +# Allow the public project files. +!migrations +!src +!Dockerfile +!LICENSE +!package*.json +!README.md +!tsconfig*.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e50a77d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Base alias to use across stages. +FROM node:20.12.2-alpine AS base + +# Dependency stage. +FROM base AS deps + +WORKDIR /tmp/app + +COPY package*.json ./ + +RUN npm ci + +# Build stage. +FROM base AS build + +WORKDIR /tmp/app + +COPY --from=deps /tmp/app/node_modules ./node_modules +COPY . . + +RUN npm run build + +# Production image. +FROM base AS runner + +WORKDIR /opt/app +ENV NODE_END=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nodejs + +COPY --from=build --chown=nodejs:nodejs /tmp/app . + +USER nodejs + +CMD ["npm", "start"] From ba9dfdf11aa22db84ee6ab5a9ad5aa96f73b7316 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 12 Aug 2024 12:54:26 -0500 Subject: [PATCH 48/52] Added new CI workflows. --- .github/workflows/callable-build.yml | 85 ++++++++++++++++++++++++++++ .github/workflows/callable-test.yml | 33 +++++++++++ .github/workflows/on-pr.yml | 14 +++++ .github/workflows/on-push-main.yml | 26 +++++++++ 4 files changed, 158 insertions(+) create mode 100644 .github/workflows/callable-build.yml create mode 100644 .github/workflows/callable-test.yml create mode 100644 .github/workflows/on-pr.yml create mode 100644 .github/workflows/on-push-main.yml diff --git a/.github/workflows/callable-build.yml b/.github/workflows/callable-build.yml new file mode 100644 index 0000000..fac4c98 --- /dev/null +++ b/.github/workflows/callable-build.yml @@ -0,0 +1,85 @@ +name: Build Docker Image + +on: + workflow_call: + inputs: + ghcr_image_name: + required: true + type: string + image_name: + required: true + type: string + image_tag: + required: true + type: string + ghcr_username: + rquired: true + type: string + dockerhub_username: + required: true + type: string + secrets: + ghcr_token: + required: true + dockerhub_token: + required: true + +jobs: + build: + name: Build Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout the Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker Layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ inputs.ghcr_username }} + password: ${{ secrets.ghcr_token }} + + - name: Login to DockerHub Registry + uses: docker/login-action@v3 + with: + username: ${{ inputs.dockerhub_username }} + password: ${{ secrets.dockerhub_token }} + + - name: Get Version from package.json + id: package-version + uses: martinbeentjes/npm-get-version-action@master + + - name: Build & Push Docker Image + id: docker_build + uses: docker/build-push-action@v3 + env: + GHCR_IMAGE: ghcr.io/${{ inputs.ghcr_image_name }} + DOCKERHUB_IMAGE: ${{ inputs.dockerhub_username }}/${{ inputs.image_name }} + with: + context: . + push: true + tags: | + ${{ env.GHCR_IMAGE }}:${{ inputs.image_tag }} + ${{ env.GHCR_IMAGE }}:${{ steps.package-version.outputs.current-version }} + ${{ env.DOCKERHUB_IMAGE }}:${{ inputs.image_tag }} + ${{ env.DOCKERHUB_IMAGE }}:${{ steps.package-version.outputs.current-version }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + platforms: linux/amd64,linux/arm64/v8 + + - name: Image Digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/callable-test.yml b/.github/workflows/callable-test.yml new file mode 100644 index 0000000..e044164 --- /dev/null +++ b/.github/workflows/callable-test.yml @@ -0,0 +1,33 @@ +name: Run Tests + +on: + workflow_call: + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout the Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install Dependencies + run: npm ci + + - name: Run Type Checks + run: npm run type-check + + - name: Run Linter + run: npm run lint + + - name: Run Tests + run: npm run test diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml new file mode 100644 index 0000000..8afa692 --- /dev/null +++ b/.github/workflows/on-pr.yml @@ -0,0 +1,14 @@ +name: On Pull Request + +on: + pull_request: + types: + - opened + - edited + - synchronize + - reopened + +jobs: + test: + name: Run Tests + uses: ./.github/workflows/callable-test.yml diff --git a/.github/workflows/on-push-main.yml b/.github/workflows/on-push-main.yml new file mode 100644 index 0000000..d2ec732 --- /dev/null +++ b/.github/workflows/on-push-main.yml @@ -0,0 +1,26 @@ +name: On Push (Main) + +on: + push: + branches: + - main + +jobs: + test: + name: Run Tests + uses: ./.github/workflows/callable-test.yml + + build: + name: Build Docker Image + uses: ./.github/workflows/callable-build.yml + needs: + - test + with: + ghcr_image_name: ${{ github.repository }} + image_name: discord-free-games-notifier + image_tag: latest + ghcr_username: ${{ github.actor }} + dockerhub_username: moonstarx + secrets: + ghcr_token: ${{ secrets.GITHUB_TOKEN }} + dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} From 0eea52f1f6bd8a6390d248bcc656be690780eaca Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 12 Aug 2024 12:54:36 -0500 Subject: [PATCH 49/52] Added old PR and issue templates. --- .github/ISSUE_TEMPLATE/bug_report.md | 33 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 21 +++++++++++++++ .github/ISSUE_TEMPLATE/question.md | 13 +++++++++ .github/PULL_REQUEST_TEMPLATE.md | 16 +++++++++++ 4 files changed, 83 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..3d5df47 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: "Bug Report" +about: Use this template to report a bug. +labels: "Type: Bug" +--- + +#### :bug: Describe the Bug + +> A clear and concise description of what the bug is. + +#### :pencil2: Steps to Reproduce + +> 1. Go to '...' +> 2. Type '...' +> 3. See error... + +#### :confused: Expected Behavior + +> A description of what you expected to get (if applicable). + +#### :scroll: Log + +``` text +Paste a relevant portion of the console log here. +``` + +#### :camera: Screenshots + +> Paste a screenshot of the bug (if applicable). + +#### :question: Other Information + +* Node Version: `Version of the node runtime.` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..409e0b2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: "Feature Request" +about: Use this template to request a new feature or if you have a suggestion. +labels: "Type: Feature Request" +--- + +#### :zap: Describe the New Feature + +> A clear and concise description of what the new feature is. + +#### :pencil2: Functionality + +> Describe the functionality of the new feature as a steps list (for easier understanding). + +> 1. Go to '...' +> 2. Type '...' +> 3. See error... + +#### :question: Additional Information + +> If applicable, you can add anything else here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..a1651b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,13 @@ +--- +name: "Question" +about: Use this template if you have any questions. Preferably use the Discord (check the README). +labels: "Type: Question" +--- + +#### :confused: Question + +> A description of what you need help with. + +#### :question: Additional Information + +> Anything additional goes here (screenshots, logs, etc...) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..5c9ab03 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +### :pencil: Checklist + +Make sure that your PR fulfills these requirements: + +- [ ] Tests have been added for this feature. +- [ ] All tests pass on your local machine. +- [ ] Code has been linted with the proper rules. +- [ ] Project builds on your local machine. + +### :page_facing_up: Description + +> Add a brief description of your PR. + +### :pushpin: Does this PR address any issue? + +> If so, add the # of the issue this is addressing. From 82dd10047c10c0733888422c51a2835a327dba46 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 12 Aug 2024 13:20:36 -0500 Subject: [PATCH 50/52] Updated README. --- README.md | 204 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 43 ++++++++++ 2 files changed, 247 insertions(+) create mode 100644 docker-compose.yml diff --git a/README.md b/README.md index 0ba0e8c..d75be5e 100644 --- a/README.md +++ b/README.md @@ -1 +1,205 @@ # Free Games Notifier for Discord + +A Discord bot that will notify your server when free games on storefronts like Steam or Epic Games come out. + +This bot depends on [free-games-crawler](https://github.com/moonstar-x/free-games-crawler) and site support depends on that service. + +For more information, please visit the [official documentation site](https://docs.moonstar-x.dev/discord-free-games-notifier). + +## Usage + +In order to use this project you'll need the following: + +* [Redis](https://redis.io) +* [Doker](https://docker.com) (Recommended) or [Node.js](https://nodejs.org) (At least version 20) + +### With Docker (Recommended) + +Create a `docker-compose.yml` file with the following: + +```yaml +services: + bot: + image: ghcr.io/moonstar-x/discord-free-games-notifier:latest + restart: unless-stopped + depends_on: + - redis + - postgres + environment: + DISCORD_TOKEN: YOUR_DISCORD_TOKEN_HERE + DISCORD_SHARING_ENABLED: false + DISCORD_SHADING_COUNT: auto + DISCORD_PRESENCE_INTERVAL: 30000 + REDIS_URI: redis://redis:6379 + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + POSTGRES_DATABASE: free-games + POSTGRES_USER: discord + POSTGRES_PASSWORD: SOMETHING_SECRET + + crawler: + image: ghcr.io/moonstar-x/free-games-crawler:latest + restart: unless-stopped + depends_on: + - redis + environment: + REDIS_URI: redis://redis:6379 + + redis: + image: redis:7.4-rc2-alpine + restart: unless-stopped + command: redis-server --save 60 1 --loglevel warning + volumes: + - ./redis:/data + + postgres: + image: postgres:15-alpine + restart: unless-stopped + volumes: + - ./postgres:/var/lib/postgresql/data + environment: + POSTGRES_DB: free-games + POSTGRES_USER: discord + POSTGRES_PASSWORD: SOMETHING_SECRET +``` + +> You can also the image `moonstarx/discord-free-games-notifier:latest` and `moonstarx/free-games-crawler:latest` if you prefer DockerHub. +> +> Make sure to replace `SOMETHING_SECRET` with a password for your database and `YOUR_DISCORD_TOKEN_HERE` with your bot's token. + +### With Node.js + +Make sure to have at least Node.js 20. + +First, clone this repository: + +```bash +git clone https://github.com/moonstar-x/discord-free-games-notifier +``` + +Install the dependencies: + +```bash +npm install +``` + +Build the project: + +```bash +npm run build +``` + +Create an `.env` file and add your configuration: + +```text +DISCORD_TOKEN=YOUR_DISCORD_TOKEN_HERE +DISCORD_SHARDING_ENABLED=false +DISCORD_SHARDING_COUNT=2 +DISCORD_PRESENCE_INTERVAL=30000 + +REDIS_URI=redis://localhost:6379 + +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_DATABASE=dev +POSTGRES_USER=dev +POSTGRES_PASSWORD=password +``` + +And start the bot: + +```bash +npm start +``` + +> This assumes you have a Redis and a Postgres instance running on `localhost`. + +### Configuration + +You can configure the bot with the following environment variables. + +| Name | Required | Default | Description | +|---------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| DISCORD_TOKEN | Yes | | The token to connect your bot to Discord. | +| DISCORD_SHARDING_ENABLED | No | false | Whether the bot should start in sharded mode or not. This is necessary if your bot is in more than 2000 servers. | +| DISCORD_SHARDING_COUNT | No | auto | The amount of shards to spawn if sharding is enabled. It should be a number greater than 1. You can leave this as `auto` to use an automatic value generated for your own needs. | +| DISCORD_PRESENCE_INTERVAL | No | 30000 | The amount of milliseconds to wait before the bot changes its presence or activity. | +| REDIS_URI | Yes | | The Redis URI shared with the crawler service. | +| POSTGRES_HOST | Yes | | The database host to connect to. | +| POSTGRES_PORT | No | 5432 | The port to use to connect to the database. | +| POSTGRES_DATABASE | Yes | | The name of the database to connect to. | +| POSTGRES_USER | Yes | | The username to connect to the database with. | +| POSTGRES_PASSWORD | Yes | | The password to connect to the database with. | + +### Commands + +Once you get the bot running you will have access to the following commands: + +| Command | Notes | Description | +|------------------------|------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| /configure channel | Guild only. Requires `ManageGuild` permission. | Set the channel to send the notifications to. Must be a text channel. | +| /configure language | Guild only. Requires `ManageGuild` permission. | Set the language in which the notifications are sent. Currently supported: English, French, and Spanish. | +| /configure storefronts | Guild only. Requires `ManageGuild` permission. | Enable or disable each storefront to be notified about. | +| /help | | Get a short help message regarding how to use the bot. | +| /info | Guild only. | Get a message with the information that the bot has stored for the server. | +| /offers | | Get a list of all the currently available game offers. | + +## Development + +Clone this repository: + +```bash +git clone https://github.com/moonstar-x/discord-free-games-notifier +``` + +Install the dependencies: + +```bash +npm install +``` + +Create the development environment: + +```bash +cd _dev && docker compose up +``` + +And run the bot locally: + +```bash +npm run dev +``` + +## Testing + +You can run unit tests by using: + +```bash +npm run test +``` + +Or, if you wish to have test watch enabled: + +```bash +npm run test:watch +``` + +## Building + +### Docker + +To build this project you should use [Docker](https://docker.com). + +To build the image locally, you can run: + +```bash +docker build -t test/discord-free-games-notifier . +``` + +### Node.js + +To build this project with Node.js, run the following command: + +```bash +npm run build +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f589a5e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,43 @@ +services: + bot: + image: ghcr.io/moonstar-x/discord-free-games-notifier:latest + restart: unless-stopped + depends_on: + - redis + - postgres + environment: + DISCORD_TOKEN: YOUR_DISCORD_TOKEN_HERE + DISCORD_SHARING_ENABLED: false + DISCORD_SHADING_COUNT: auto + DISCORD_PRESENCE_INTERVAL: 30000 + REDIS_URI: redis://redis:6379 + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + POSTGRES_DATABASE: free-games + POSTGRES_USER: discord + POSTGRES_PASSWORD: SOMETHING_SECRET + + crawler: + image: ghcr.io/moonstar-x/free-games-crawler:latest + restart: unless-stopped + depends_on: + - redis + environment: + REDIS_URI: redis://redis:6379 + + redis: + image: redis:7.4-rc2-alpine + restart: unless-stopped + command: redis-server --save 60 1 --loglevel warning + volumes: + - ./redis:/data + + postgres: + image: postgres:15-alpine + restart: unless-stopped + volumes: + - ./postgres:/var/lib/postgresql/data + environment: + POSTGRES_DB: free-games + POSTGRES_USER: discord + POSTGRES_PASSWORD: SOMETHING_SECRET From fe06a2f6dd7dba1234fa00c0779bcefef84418f7 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 12 Aug 2024 15:05:23 -0500 Subject: [PATCH 51/52] Added translations to Spanish and French. --- src/i18n/strings/es.ts | 71 ++++++++++++++++++++++++++++++++++++++++++ src/i18n/strings/fr.ts | 71 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/src/i18n/strings/es.ts b/src/i18n/strings/es.ts index 6d916df..4cb67a5 100644 --- a/src/i18n/strings/es.ts +++ b/src/i18n/strings/es.ts @@ -1,3 +1,74 @@ export default { + 'base.command.error.message': 'Un error ha ocurrido al ejecutar el comando {name}.', + 'commands.configure.name': 'configurar', + 'commands.configure.description': 'Cambia la configuración para este servidor.', + 'commands.configure.sub.channel.name': 'canal', + 'commands.configure.sub.channel.description': 'Cambia el canal de notificaciones.', + 'commands.configure.sub.channel.options.channel.name': 'canal', + 'commands.configure.sub.channel.options.channel.description': 'El canal de texto a usar.', + 'commands.configure.sub.storefronts.name': 'tiendas', + 'commands.configure.sub.storefronts.description': 'Activa o desactiva las notificaciones de cada tienda.', + 'commands.configure.sub.language.name': 'idioma', + 'commands.configure.sub.language.description': 'Cambia el idioma para las notificaciones.', + 'commands.configure.sub.language.options.language.name': 'idioma', + 'commands.configure.sub.language.options.language.description': 'El idioma en el cual se enviarán las notificaciones.', + 'commands.configure.run.channel.pre_check.text': 'Canal no provisto.', + 'commands.configure.run.channel.success.text': 'Se ha cambiado el canal de notificaciones a {channel} exitosamente.', + 'commands.configure.run.storefronts.empty.text': 'Ninguna tienda está disponible ahora.', + 'commands.configure.run.storefronts.start.text': 'Recibirás un mensaje for cada tienda disponible. Por favor, haz clic en los botones conforme aparezcan para activar o desactivar las notificaciones para cada tienda.', + 'commands.configure.run.storefronts.buttons.enable.label': 'Activar', + 'commands.configure.run.storefronts.buttons.disable.label': 'Desactivar', + 'commands.configure.run.storefronts.follow_up.question.text': 'Deseas recibir notificaciones para la tienda **{storefront}**?', + 'commands.configure.run.storefronts.response.update.positive.text': 'Este servidor recibirá notificaciones para la tienda **{storefront}**.', + 'commands.configure.run.storefronts.response.update.negative.text': 'Este servidor ya no recibirá notificaciones para la tienda **{storefront}**.', + 'commands.configure.run.language.pre_check.text': 'Idioma no provisto.', + 'commands.configure.run.language.success.text': 'Se ha cambiado el idioma de notificaciones a **{language}** exitosamente.', + 'commands.configure.run.default.response.text': 'Subcomando desconocido recibido.', + + 'commands.help.name': 'ayuda', + 'commands.help.description': 'Información de ayuda y de uso.', + 'commands.help.run.embed.title': 'Notificaciones de Juegos Gratis', + 'commands.help.run.embed.fields.0.name': 'Información básica', + 'commands.help.run.embed.fields.0.value': 'Este bot enviará mensajes cuando un juego se vuelva gratis en varias tiendas. Para usarlo, asegúrate de correr el comando **/configurar canal** para cambiar el canal de la subscripción en donde enviar los mensajes. También puedes escoger de qué tiendas deseas recibir notificaciones.', + 'commands.help.run.embed.fields.1.name': '¿Encontraste un error?', + 'commands.help.run.embed.fields.1.value': 'Si encontraste algo roto, tienes una petición de funcionalidad o sugerencias, no dudes en reportarlos en el repositorio de GitHub.', + 'commands.help.run.buttons.bot_issues.label': '¿Encontraste un error con el bot?', + 'commands.help.run.buttons.crawler_issues.label': '¿Encontraste un error con las notificaciones?', + 'commands.help.run.buttons.bot_website.label': 'Sitio Oficial', + + 'commands.info.name': 'info', + 'commands.info.description': 'Obtén la información de ajustes guardada para este servidor.', + 'commands.info.run.pre_check.text': 'Ningún ajuste encontrado para este servidor. Por favor usa el comando **/configurar canal** para cambiar el canal de subscripción.', + 'commands.info.run.embed.title': 'Ajustes para Notificaciones de Juegos Gratis', + 'commands.info.run.embed.fields.server.name': 'Servidor', + 'commands.info.run.embed.fields.channel.name': 'Canal de Subscripción', + 'commands.info.run.embed.fields.channel.value.unset': 'No configurado', + 'commands.info.run.embed.fields.locale.name': 'Idioma de subscripción', + 'commands.info.run.embed.fields.subscriptions.name': 'Subscripciones', + 'commands.info.run.embed.fields.subscriptions.value': 'A continuación la lista de las tiendas a las cuales el servidor está subscrito.', + 'commands.info.run.embed.fields.storefronts.value.enabled': '✅ Activado', + 'commands.info.run.embed.fields.storefronts.value.disabled': '❌ Desactivado', + 'commands.info.run.embed.footer': 'Creado: {createdAt}\nÚltima actualización: {updatedAt}', + + 'commands.offers.name': 'ofertas', + 'commands.offers.description': 'Obtén una lista de juegos ofrecidos actualmente.', + 'commands.offers.run.empty.text': 'Actualmente no hay ningún juego gratis en las tiendas: {list}.', + 'commands.offers.run.start.text': 'A continuación la lista de las ofertas disponibles actualmente.', + + 'offers.embed.title': '{game} en {storefront}', + 'offers.embed.fields.publisher.name': 'Editor', + 'offers.embed.fields.publisher.name.unknown': 'N/A', + 'offers.embed.fields.price.name': 'Precio Original', + 'offers.embed.fields.price.name.unknown': 'N/A', + 'offers.embed.fields.type.name': 'Tipo', + 'offers.embed.fields.type.value.game': '👾️ Juego', + 'offers.embed.fields.type.value.dlc': '➕ DLC', + 'offers.embed.fields.type.value.bundle': '💰 Paquete', + 'offers.embed.fields.type.value.other': '❓ Otro', + 'offers.buttons.get.label': 'Obténlo en {storefront}!', + + 'locales.names.english': 'Inglés', + 'locales.names.spanish': 'Español', + 'locales.names.french': 'Francés' }; diff --git a/src/i18n/strings/fr.ts b/src/i18n/strings/fr.ts index 6d916df..e99c9bb 100644 --- a/src/i18n/strings/fr.ts +++ b/src/i18n/strings/fr.ts @@ -1,3 +1,74 @@ export default { + 'base.command.error.message': "Une erreur s'est produite lors de l'exécution de la commande {name}.", + 'commands.configure.name': 'configurer', + 'commands.configure.description': 'Modifier la configuration de ce serveur.', + 'commands.configure.sub.channel.name': 'canal', + 'commands.configure.sub.channel.description': 'Changer le canal des notifications.', + 'commands.configure.sub.channel.options.channel.name': 'canal', + 'commands.configure.sub.channel.options.channel.description': 'Le canal texte à utiliser.', + 'commands.configure.sub.storefronts.name': 'boutiques', + 'commands.configure.sub.storefronts.description': 'Activer ou désactiver les notifications pour chaque boutique.', + 'commands.configure.sub.language.name': 'langue', + 'commands.configure.sub.language.description': 'Changer la langue des notifications.', + 'commands.configure.sub.language.options.language.name': 'langue', + 'commands.configure.sub.language.options.language.description': 'La langue dans laquelle envoyer les notifications.', + 'commands.configure.run.channel.pre_check.text': 'Aucun canal fourni.', + 'commands.configure.run.channel.success.text': 'Le canal des notifications a été mis à jour avec succès à {channel}.', + 'commands.configure.run.storefronts.empty.text': "Aucune boutique n'est disponible pour le moment.", + 'commands.configure.run.storefronts.start.text': 'Vous recevrez un message pour chaque boutique disponible. Veuillez cliquer sur les boutons au fur et à mesure pour activer ou désactiver les notifications pour chaque boutique.', + 'commands.configure.run.storefronts.buttons.enable.label': 'Activer', + 'commands.configure.run.storefronts.buttons.disable.label': 'Désactiver', + 'commands.configure.run.storefronts.follow_up.question.text': 'Souhaitez-vous recevoir des notifications pour **{storefront}** ?', + 'commands.configure.run.storefronts.response.update.positive.text': 'Ce serveur recevra des notifications pour **{storefront}**.', + 'commands.configure.run.storefronts.response.update.negative.text': 'Ce serveur ne recevra plus de notifications pour **{storefront}**.', + 'commands.configure.run.language.pre_check.text': 'Aucune langue fournie.', + 'commands.configure.run.language.success.text': 'La langue des notifications a été mise à jour avec succès en **{language}**.', + 'commands.configure.run.default.response.text': 'Sous-commande inconnue reçue.', + + 'commands.help.name': 'aide', + 'commands.help.description': "Informations d'aide et d'utilisation.", + 'commands.help.run.embed.title': 'Notifications de Jeux Gratuits', + 'commands.help.run.embed.fields.0.name': 'Informations de base', + 'commands.help.run.embed.fields.0.value': "Ce bot enverra des messages dès qu'un jeu devient gratuit sur différentes boutiques de jeux. Pour l'utiliser, assurez-vous de lancer la commande **/configurer canal** pour définir le canal d'abonnement pour envoyer les notifications. Vous pouvez également choisir les boutiques pour lesquelles vous souhaitez être notifié.", + 'commands.help.run.embed.fields.1.name': 'Trouvé un bug ?', + 'commands.help.run.embed.fields.1.value': "Si vous avez trouvé un problème, avez des demandes de fonctionnalités ou des suggestions, n'hésitez pas à les signaler dans le repo GitHub.", + 'commands.help.run.buttons.bot_issues.label': 'Trouvé un bug avec le bot ?', + 'commands.help.run.buttons.crawler_issues.label': "Trouvé un bug avec les notifications d'offres de jeux ?", + 'commands.help.run.buttons.bot_website.label': 'Site officiel', + + 'commands.info.name': 'infos', + 'commands.info.description': 'Obtenez les informations des paramètres enregistrés pour ce serveur.', + 'commands.info.run.pre_check.text': "Aucun paramètre trouvé pour ce serveur. Veuillez utiliser la commande **/configurer canal** pour configurer le canal d'abonnement.", + 'commands.info.run.embed.title': 'Paramètres pour les Notifications de Jeux Gratuits', + 'commands.info.run.embed.fields.server.name': 'Serveur', + 'commands.info.run.embed.fields.channel.name': "Canal d'Abonnement", + 'commands.info.run.embed.fields.channel.value.unset': 'Non défini', + 'commands.info.run.embed.fields.locale.name': "Langue d'Abonnement", + 'commands.info.run.embed.fields.subscriptions.name': 'Abonnements', + 'commands.info.run.embed.fields.subscriptions.value': 'Voici une liste de toutes les boutiques auxquelles ce serveur est abonné.', + 'commands.info.run.embed.fields.storefronts.value.enabled': '✅ Activé', + 'commands.info.run.embed.fields.storefronts.value.disabled': '❌ Désactivé', + 'commands.info.run.embed.footer': 'Créé : {createdAt}\nDernière modification : {updatedAt}', + + 'commands.offers.name': 'offres', + 'commands.offers.description': 'Obtenez une liste des jeux gratuits actuellement offerts.', + 'commands.offers.run.empty.text': "Il n'y a actuellement aucune offre dans les boutiques suivantes : {list}.", + 'commands.offers.run.start.text': 'Voici une liste des offres actuellement disponibles.', + + 'offers.embed.title': '{game} sur {storefront}', + 'offers.embed.fields.publisher.name': 'Éditeur', + 'offers.embed.fields.publisher.name.unknown': 'N/A', + 'offers.embed.fields.price.name': "Prix d'origine", + 'offers.embed.fields.price.name.unknown': 'N/A', + 'offers.embed.fields.type.name': 'Type', + 'offers.embed.fields.type.value.game': '👾️ Jeu', + 'offers.embed.fields.type.value.dlc': '➕ DLC', + 'offers.embed.fields.type.value.bundle': '💰 Pack', + 'offers.embed.fields.type.value.other': '❓ Autre', + 'offers.buttons.get.label': 'Obtenez-le sur {storefront} !', + + 'locales.names.english': 'Anglais', + 'locales.names.spanish': 'Espagnol', + 'locales.names.french': 'Français' }; From 824ae00ed60860c49cb86fd18cfbd10cf19e3aa6 Mon Sep 17 00:00:00 2001 From: moonstar-x <christian.lopez99@outlook.com> Date: Mon, 12 Aug 2024 15:18:34 -0500 Subject: [PATCH 52/52] Added TZ to test runner in CI. --- .github/workflows/callable-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/callable-test.yml b/.github/workflows/callable-test.yml index e044164..f4be59c 100644 --- a/.github/workflows/callable-test.yml +++ b/.github/workflows/callable-test.yml @@ -31,3 +31,5 @@ jobs: - name: Run Tests run: npm run test + env: + TZ: America/Guayaquil